Skip to content

Commit b17124b

Browse files
markruihackorum
authored andcommitted
Separate catalog_xmin from xmin via ephemeral physical slot
When a standby connects without a replication slot and sends hot standby feedback, lazily create an ephemeral physical slot so xmin and catalog_xmin can be tracked separately and atomically (under the slot mutex), instead of conflating both into the walsender's PGPROC xmin. Add an error_if_full flag to ReplicationSlotCreate so the walsender can degrade gracefully to the legacy min(xmin, catalog_xmin) behavior when the slot pool is exhausted, rather than tearing down the connection.
1 parent 1f3b9bb commit b17124b

7 files changed

Lines changed: 49 additions & 19 deletions

File tree

src/backend/commands/repack_worker.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ repack_setup_logical_decoding(Oid relid)
220220
*/
221221
snprintf(slotname, NAMEDATALEN, "pg_repack_%d", MyProcPid);
222222
ReplicationSlotCreate(slotname, true, RS_TEMPORARY, false, true,
223-
false, false);
223+
false, false, true);
224224
EnsureLogicalDecodingEnabled();
225225

226226
/*

src/backend/replication/logical/launcher.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,7 @@ CreateConflictDetectionSlot(void)
15761576
errmsg("creating replication conflict detection slot"));
15771577

15781578
ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
1579-
false, false, false);
1579+
false, false, false, true);
15801580

15811581
init_conflict_slot_xmin();
15821582
}

src/backend/replication/logical/slotsync.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
899899
remote_slot->two_phase,
900900
false,
901901
remote_slot->failover,
902-
true);
902+
true, true);
903903

904904
/* For shorter lines. */
905905
slot = MyReplicationSlot;

src/backend/replication/slot.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,11 +373,17 @@ IsSlotForConflictCheck(const char *name)
373373
* failover: If enabled, allows the slot to be synced to standbys so
374374
* that logical replication can be resumed after failover.
375375
* synced: True if the slot is synchronized from the primary server.
376+
* error_if_full: If true, raise an error when no slot is free; if false,
377+
* return false instead so the caller can degrade gracefully.
378+
*
379+
* Returns true if a slot was created, false only when error_if_full is false
380+
* and the slot pool is exhausted.
376381
*/
377-
void
382+
bool
378383
ReplicationSlotCreate(const char *name, bool db_specific,
379384
ReplicationSlotPersistency persistency,
380-
bool two_phase, bool repack, bool failover, bool synced)
385+
bool two_phase, bool repack, bool failover, bool synced,
386+
bool error_if_full)
381387
{
382388
ReplicationSlot *slot = NULL;
383389
int startpoint,
@@ -458,11 +464,18 @@ ReplicationSlotCreate(const char *name, bool db_specific,
458464

459465
/* If all slots are in use, we're out of luck. */
460466
if (slot == NULL)
467+
{
468+
if (!error_if_full)
469+
{
470+
LWLockRelease(ReplicationSlotAllocationLock);
471+
return false;
472+
}
461473
ereport(ERROR,
462474
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
463475
errmsg("all replication slots are in use"),
464476
errhint("Free one or increase \"%s\".",
465477
repack ? "max_repack_replication_slots" : "max_replication_slots")));
478+
}
466479

467480
/*
468481
* Since this slot is not in use, nobody should be looking at any part of
@@ -539,6 +552,8 @@ ReplicationSlotCreate(const char *name, bool db_specific,
539552

540553
/* Let everybody know we've modified this slot */
541554
ConditionVariableBroadcast(&slot->active_cv);
555+
556+
return true;
542557
}
543558

544559
/*

src/backend/replication/slotfuncs.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
5353
/* acquire replication slot, this will check for conflicting names */
5454
ReplicationSlotCreate(name, false,
5555
temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
56-
false, false, false);
56+
false, false, false, true);
5757

5858
if (immediately_reserve)
5959
{
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
146146
*/
147147
ReplicationSlotCreate(name, true,
148148
temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
149-
false, failover, false);
149+
false, failover, false, true);
150150

151151
/*
152152
* Ensure the logical decoding is enabled before initializing the logical

src/backend/replication/walsender.c

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,7 +1286,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
12861286
{
12871287
ReplicationSlotCreate(cmd->slotname, false,
12881288
cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
1289-
false, false, false, false);
1289+
false, false, false, false, true);
12901290

12911291
if (reserve_wal)
12921292
{
@@ -1317,7 +1317,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
13171317
*/
13181318
ReplicationSlotCreate(cmd->slotname, true,
13191319
cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
1320-
two_phase, false, failover, false);
1320+
two_phase, false, failover, false, true);
13211321

13221322
/*
13231323
* Do options check early so that we can bail before calling the
@@ -2826,18 +2826,33 @@ ProcessStandbyHSFeedbackMessage(void)
28262826
* obviously safe, and if we're moving it backwards, well, the data is at
28272827
* risk already since a VACUUM could already have determined the horizon.)
28282828
*
2829-
* If we're using a replication slot we reserve the xmin via that,
2830-
* otherwise via the walsender's PGPROC entry. We can only track the
2831-
* catalog xmin separately when using a slot, so we store the least of the
2832-
* two provided when not using a slot.
2833-
*
2834-
* XXX: It might make sense to generalize the ephemeral slot concept and
2835-
* always use the slot mechanism to handle the feedback xmin.
2829+
* If we're using a replication slot we reserve the xmin via that. When the
2830+
* standby connected without one, lazily create an ephemeral physical slot
2831+
* here, so that we can still track xmin and catalog_xmin separately (and
2832+
* atomically, under the slot mutex). The ephemeral slot is dropped
2833+
* automatically when this walsender exits.
28362834
*/
2837-
if (MyReplicationSlot != NULL) /* XXX: persistency configurable? */
2835+
if (MyReplicationSlot == NULL)
2836+
{
2837+
char slotname[NAMEDATALEN];
2838+
2839+
snprintf(slotname, sizeof(slotname), "pg_walsender_%d", MyProcPid);
2840+
ReplicationSlotCreate(slotname, false, RS_EPHEMERAL,
2841+
false, false, false, false, false);
2842+
}
2843+
2844+
if (MyReplicationSlot != NULL)
28382845
PhysicalReplicationSlotNewXmin(feedbackXmin, feedbackCatalogXmin);
28392846
else
28402847
{
2848+
/*
2849+
* The slot pool is exhausted, so we cannot track the two horizons
2850+
* separately. Degrade gracefully to the pre-existing behavior of
2851+
* holding back both via the walsender's PGPROC entry, using the
2852+
* older of the two values. This loses the catalog/data separation
2853+
* for this standby until a slot frees up, but never breaks the
2854+
* connection.
2855+
*/
28412856
if (TransactionIdIsNormal(feedbackCatalogXmin)
28422857
&& TransactionIdPrecedes(feedbackCatalogXmin, feedbackXmin))
28432858
MyProc->xmin = feedbackCatalogXmin;

src/include/replication/slot.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,10 @@ extern PGDLLIMPORT char *synchronized_standby_slots;
329329
extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
330330

331331
/* management of individual slots */
332-
extern void ReplicationSlotCreate(const char *name, bool db_specific,
332+
extern bool ReplicationSlotCreate(const char *name, bool db_specific,
333333
ReplicationSlotPersistency persistency,
334334
bool two_phase, bool repack, bool failover,
335-
bool synced);
335+
bool synced, bool error_if_full);
336336
extern void ReplicationSlotPersist(void);
337337
extern void ReplicationSlotDrop(const char *name, bool nowait);
338338
extern void ReplicationSlotDropAcquired(bool try_disable);

0 commit comments

Comments
 (0)