agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v44 01/10] Make index_concurrently_create_copy more general
420+ messages / 1 participants
[nested] [flat]

* [PATCH v44 01/10] Make index_concurrently_create_copy more general
@ 2026-03-24 18:02  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-03-24 18:02 UTC (permalink / raw)

Add a 'boolean concurrent' option, and make it work for both cases.
Also rename it to index_create_copy.

This allows it to be reused for other purposes -- specifically, for
REPACK CONCURRENTLY.

With the CONCURRENTLY option, REPACK cannot simply swap the heap file and
rebuild its indexes. Instead, it needs to build a separate set of indexes
(including system catalog entries) *before* the actual swap, to reduce the
time AccessExclusiveLock needs to be held for.  This approach is
different from what CREATE INDEX CONCURRENTLY does.

Per a suggestion from Mihail Nikalayeu.

Author: Antonin Houska <ah@cybertec.at>
Discussion: https://postgr.es/m/41104.1754922120@localhost
---
 src/backend/catalog/index.c      | 39 +++++++++++++++++++-------------
 src/backend/commands/indexcmds.c | 15 +++++++-----
 src/backend/nodes/makefuncs.c    |  9 ++++----
 src/include/catalog/index.h      |  7 +++---
 src/include/nodes/makefuncs.h    |  4 +++-
 5 files changed, 43 insertions(+), 31 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d8219b18c48..de7182a85a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1289,17 +1289,17 @@ index_create(Relation heapRelation,
 }
 
 /*
- * index_concurrently_create_copy
+ * index_create_copy
  *
- * Create concurrently an index based on the definition of the one provided by
- * caller.  The index is inserted into catalogs and needs to be built later
- * on.  This is called during concurrent reindex processing.
+ * Create an index based on the definition of the one provided by caller.  The
+ * index is inserted into catalogs. If 'concurrently' is TRUE, it needs to be
+ * built later on; otherwise it's built immediately.
  *
  * "tablespaceOid" is the tablespace to use for this index.
  */
 Oid
-index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
-							   Oid tablespaceOid, const char *newName)
+index_create_copy(Relation heapRelation, bool concurrently,
+				  Oid oldIndexId, Oid tablespaceOid, const char *newName)
 {
 	Relation	indexRelation;
 	IndexInfo  *oldInfo,
@@ -1318,6 +1318,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	List	   *indexColNames = NIL;
 	List	   *indexExprs = NIL;
 	List	   *indexPreds = NIL;
+	int			flags = 0;
 
 	indexRelation = index_open(oldIndexId, RowExclusiveLock);
 
@@ -1328,7 +1329,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	 * Concurrent build of an index with exclusion constraints is not
 	 * supported.
 	 */
-	if (oldInfo->ii_ExclusionOps != NULL)
+	if (oldInfo->ii_ExclusionOps != NULL && concurrently)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("concurrent index creation for exclusion constraints is not supported")));
@@ -1384,9 +1385,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	}
 
 	/*
-	 * Build the index information for the new index.  Note that rebuild of
-	 * indexes with exclusion constraints is not supported, hence there is no
-	 * need to fill all the ii_Exclusion* fields.
+	 * Build the index information for the new index.
 	 */
 	newInfo = makeIndexInfo(oldInfo->ii_NumIndexAttrs,
 							oldInfo->ii_NumIndexKeyAttrs,
@@ -1395,10 +1394,13 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							indexPreds,
 							oldInfo->ii_Unique,
 							oldInfo->ii_NullsNotDistinct,
-							false,	/* not ready for inserts */
-							true,
+							!concurrently,	/* isready */
+							concurrently,	/* concurrent */
 							indexRelation->rd_indam->amsummarizing,
-							oldInfo->ii_WithoutOverlaps);
+							oldInfo->ii_WithoutOverlaps,
+							oldInfo->ii_ExclusionOps,
+							oldInfo->ii_ExclusionProcs,
+							oldInfo->ii_ExclusionStrats);
 
 	/*
 	 * Extract the list of column names and the column numbers for the new
@@ -1436,6 +1438,9 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 		stattargets[i].isnull = isnull;
 	}
 
+	if (concurrently)
+		flags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT;
+
 	/*
 	 * Now create the new index.
 	 *
@@ -1459,7 +1464,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							  indcoloptions->values,
 							  stattargets,
 							  reloptionsDatum,
-							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
+							  flags,
 							  0,
 							  true, /* allow table to be a system catalog? */
 							  false,	/* is_internal? */
@@ -2453,7 +2458,8 @@ BuildIndexInfo(Relation index)
 					   indexStruct->indisready,
 					   false,
 					   index->rd_indam->amsummarizing,
-					   indexStruct->indisexclusion && indexStruct->indisunique);
+					   indexStruct->indisexclusion && indexStruct->indisunique,
+					   NULL, NULL, NULL);
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
@@ -2513,7 +2519,8 @@ BuildDummyIndexInfo(Relation index)
 					   indexStruct->indisready,
 					   false,
 					   index->rd_indam->amsummarizing,
-					   indexStruct->indisexclusion && indexStruct->indisunique);
+					   indexStruct->indisexclusion && indexStruct->indisunique,
+					   NULL, NULL, NULL);
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index dd593ccbc1c..83edab38760 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -244,7 +244,8 @@ CheckIndexCompatible(Oid oldId,
 	 */
 	indexInfo = makeIndexInfo(numberOfAttributes, numberOfAttributes,
 							  accessMethodId, NIL, NIL, false, false,
-							  false, false, amsummarizing, isWithoutOverlaps);
+							  false, false, amsummarizing, isWithoutOverlaps,
+							  NULL, NULL, NULL);
 	typeIds = palloc_array(Oid, numberOfAttributes);
 	collationIds = palloc_array(Oid, numberOfAttributes);
 	opclassIds = palloc_array(Oid, numberOfAttributes);
@@ -931,7 +932,8 @@ DefineIndex(ParseState *pstate,
 							  !concurrent,
 							  concurrent,
 							  amissummarizing,
-							  stmt->iswithoutoverlaps);
+							  stmt->iswithoutoverlaps,
+							  NULL, NULL, NULL);
 
 	typeIds = palloc_array(Oid, numberOfAttributes);
 	collationIds = palloc_array(Oid, numberOfAttributes);
@@ -3989,10 +3991,11 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 			tablespaceid = indexRel->rd_rel->reltablespace;
 
 		/* Create new index definition based on given index */
-		newIndexId = index_concurrently_create_copy(heapRel,
-													idx->indexId,
-													tablespaceid,
-													concurrentName);
+		newIndexId = index_create_copy(heapRel,
+									   true,
+									   idx->indexId,
+									   tablespaceid,
+									   concurrentName);
 
 		/*
 		 * Now open the relation of the new index, a session-level lock is
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 3cd35c5c457..8d23aa917e5 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -834,7 +834,8 @@ IndexInfo *
 makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 			  List *predicates, bool unique, bool nulls_not_distinct,
 			  bool isready, bool concurrent, bool summarizing,
-			  bool withoutoverlaps)
+			  bool withoutoverlaps, Oid *exclusion_ops, Oid *exclusion_procs,
+			  uint16 *exclusion_strats)
 {
 	IndexInfo  *n = makeNode(IndexInfo);
 
@@ -863,9 +864,9 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_PredicateState = NULL;
 
 	/* exclusion constraints */
-	n->ii_ExclusionOps = NULL;
-	n->ii_ExclusionProcs = NULL;
-	n->ii_ExclusionStrats = NULL;
+	n->ii_ExclusionOps = exclusion_ops;
+	n->ii_ExclusionProcs = exclusion_procs;
+	n->ii_ExclusionStrats = exclusion_strats;
 
 	/* speculative inserts */
 	n->ii_UniqueOps = NULL;
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 36b70689254..56a064ef444 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -101,10 +101,9 @@ extern Oid	index_create(Relation heapRelation,
 #define	INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS	(1 << 4)
 #define	INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS (1 << 5)
 
-extern Oid	index_concurrently_create_copy(Relation heapRelation,
-										   Oid oldIndexId,
-										   Oid tablespaceOid,
-										   const char *newName);
+extern Oid	index_create_copy(Relation heapRelation, bool concurrently,
+							  Oid oldIndexId, Oid tablespaceOid,
+							  const char *newName);
 
 extern void index_concurrently_build(Oid heapRelationId,
 									 Oid indexRelationId);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index bf54d39feb0..40ec249a7a1 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -99,7 +99,9 @@ extern IndexInfo *makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid,
 								List *expressions, List *predicates,
 								bool unique, bool nulls_not_distinct,
 								bool isready, bool concurrent,
-								bool summarizing, bool withoutoverlaps);
+								bool summarizing, bool withoutoverlaps,
+								Oid *exclusion_ops, Oid *exclusion_procs,
+								uint16 *exclusion_strats);
 
 extern Node *makeStringConst(char *str, int location);
 extern DefElem *makeDefElem(char *name, Node *arg, int location);
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0002-Do-not-dereference-varattrib_4b-in-VARSIZE_4B.patch"



^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread

* [PATCH v49 7/7] WIP add max_repack_replication_slots
@ 2026-04-01 17:54  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 420+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

---
 src/backend/commands/repack_worker.c          |  4 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 77 +++++++++++--------
 src/backend/replication/slotfuncs.c           |  8 +-
 src/backend/replication/walsender.c           |  4 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/slot.h                |  6 +-
 9 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 106b2b60441..00b21ede481 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -228,8 +228,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..d83125afd0d 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..1bc7f3f600d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -425,7 +425,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -814,6 +814,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1691,7 +1692,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..fe6bfeba25e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -151,6 +151,9 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 1;	/* the maximum number of slots
+												 * for REPACK */
+int			TotalMaxReplicationSlots = 0;	/* sum of both */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -190,12 +193,14 @@ ReplicationSlotsShmemSize(void)
 {
 	Size		size = 0;
 
-	if (max_replication_slots == 0)
+	TotalMaxReplicationSlots = max_replication_slots + max_repack_replication_slots;
+
+	if (TotalMaxReplicationSlots == 0)
 		return size;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(TotalMaxReplicationSlots, sizeof(ReplicationSlot)));
 
 	return size;
 }
@@ -208,7 +213,7 @@ ReplicationSlotsShmemInit(void)
 {
 	bool		found;
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return;
 
 	ReplicationSlotCtl = (ReplicationSlotCtlData *)
@@ -222,7 +227,7 @@ ReplicationSlotsShmemInit(void)
 		/* First time through, so initialize */
 		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
 
-		for (i = 0; i < max_replication_slots; i++)
+		for (i = 0; i < TotalMaxReplicationSlots; i++)
 		{
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -372,6 +377,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -379,10 +385,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -431,12 +438,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = repack ? max_replication_slots : 0;
+	endpoint = repack ? TotalMaxReplicationSlots : max_replication_slots;
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -444,7 +455,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -547,7 +560,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -575,7 +588,7 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots + TotalMaxReplicationSlots);
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -869,7 +882,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1251,7 +1264,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1306,7 +1319,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1373,12 +1386,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1453,11 +1466,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1514,13 +1527,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1617,11 +1630,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1662,7 +1675,10 @@ CheckSlotRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	/* FIXME how to relax this for repack in a way that doesn't mess everything
+	 * up?
+	 */
+	if (TotalMaxReplicationSlots == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
@@ -2216,7 +2232,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (TotalMaxReplicationSlots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2224,7 +2240,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2329,7 +2345,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2430,7 +2446,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (TotalMaxReplicationSlots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
@@ -2860,7 +2876,7 @@ RestoreSlotFromDisk(const char *name)
 				 errhint("Change \"wal_level\" to be \"replica\" or higher.")));
 
 	/* nothing can be active yet, don't lock anything */
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *slot;
 
@@ -2902,6 +2918,7 @@ RestoreSlotFromDisk(const char *name)
 		break;
 	}
 
+	/* XXX might be misleading if the slots previously in use were REPACK. */
 	if (!restored)
 		ereport(FATAL,
 				(errmsg("too many replication slots active before shutdown"),
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..cdb4dbd87c9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -270,7 +270,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < TotalMaxReplicationSlots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -665,7 +665,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < TotalMaxReplicationSlots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fbbe09135bf..caea3ed1257 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1220,7 +1220,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1251,7 +1251,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0a862693fcd..d12a3bd7bdf 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2070,6 +2070,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '1',
+  min => '0',
+  max => 'MAX_BACKENDS /* XXX? */',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf15597385b..15014f37753 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 1 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..88953576894 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,9 +324,13 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
+/* only for slotfuncs.c, slotsync.c etc */
+extern int TotalMaxReplicationSlots;
+
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
 extern void ReplicationSlotsShmemInit(void);
@@ -334,7 +338,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-- 
2.47.3


--3n3vqr5y2jhknrqj--





^ permalink  raw  reply  [nested|flat] 420+ messages in thread


end of thread, other threads:[~2026-04-01 17:54 UTC | newest]

Thread overview: 420+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-24 18:02 [PATCH v44 01/10] Make index_concurrently_create_copy more general Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 17:54 [PATCH v49 7/7] WIP add max_repack_replication_slots Álvaro Herrera <alvherre@kurilemu.de>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox