From 2edaf6d9a427c9f683989bd8eb1ed4da9579541a Mon Sep 17 00:00:00 2001 From: Mikhail Nikalayeu Date: Sun, 1 Feb 2026 14:21:41 +0100 Subject: [PATCH vX] Handle VACUUM interaction with REPACK CONCURRENTLY and separate catalog/data horizons, based on d9d076222f5 --- src/backend/commands/cluster.c | 32 ++++++++++++++++++++++++- src/backend/storage/ipc/procarray.c | 36 +++++++++++++++++++++++------ src/include/storage/proc.h | 6 +++-- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 403bdcda32e..5afd6e8088f 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -292,7 +292,7 @@ static void export_snapshot(Snapshot snapshot, DecodingWorkerShared *shared); static void ProcessRepackMessage(StringInfo msg); static const char *RepackCommandAsString(RepackCommand cmd); - +static void set_in_repack_procflags(void); #define REPL_PLUGIN_NAME "pgoutput_repack" @@ -355,6 +355,14 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) parser_errposition(pstate, opt->location)); } + if ((params.options & CLUOPT_CONCURRENT) != 0) + { + InvalidateCatalogSnapshot(); + PopActiveSnapshot(); + set_in_repack_procflags(); + PushActiveSnapshot(GetTransactionSnapshot()); + } + /* * Determine the lock mode expected by cluster_rel(). * @@ -509,6 +517,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) continue; } + if ((params.options & CLUOPT_CONCURRENT) != 0) + { + InvalidateCatalogSnapshot(); + set_in_repack_procflags(); + } + /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); @@ -4357,3 +4371,19 @@ ProcessRepackMessage(StringInfo msg) } } } + +static void +set_in_repack_procflags(void) +{ + /* + * This should only be called before installing xid or xmin in MyProc; + * otherwise, concurrent processes could see an Xmin that moves backwards. + */ + Assert(MyProc->xid == InvalidTransactionId && + MyProc->xmin == InvalidTransactionId); + + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + MyProc->statusFlags |= PROC_IN_REPACK; + ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; + LWLockRelease(ProcArrayLock); +} diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 6be565155ab..9cede06338a 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -1642,7 +1642,12 @@ TransactionIdIsInProgress(TransactionId xid) * relations that's not required, since only backends in my own database could * ever see the tuples in them. Also, we can ignore concurrently running lazy * VACUUMs because (a) they must be working on other tables, and (b) they - * don't need to do snapshot-based lookups. + * don't need to do snapshot-based lookups. Similarly, for the non-catalog + * horizon, we can ignore REPACK CONCURRENTLY for the + * same reasons and because they can't run in transaction blocks. (They are + * not possible to ignore for catalogs, because REPACK do some catalog + * operations.) Do note that this means that REPACK must use a lock level + * that conflicts with VACUUM. * * This also computes a horizon used to truncate pg_subtrans. For that * backends in all databases have to be considered, and concurrently running @@ -1689,9 +1694,6 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) bool in_recovery = RecoveryInProgress(); TransactionId *other_xids = ProcGlobal->xids; - /* inferred after ProcArrayLock is released */ - h->catalog_oldest_nonremovable = InvalidTransactionId; - LWLockAcquire(ProcArrayLock, LW_SHARED); h->latest_completed = TransamVariables->latestCompletedXid; @@ -1711,6 +1713,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->oldest_considered_running = initial; h->shared_oldest_nonremovable = initial; + h->catalog_oldest_nonremovable = initial; h->data_oldest_nonremovable = initial; /* @@ -1809,11 +1812,26 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) (statusFlags & PROC_AFFECTS_ALL_HORIZONS) || in_recovery) { - h->data_oldest_nonremovable = - TransactionIdOlder(h->data_oldest_nonremovable, xmin); + /* + * We can ignore this backend if it's running REPACK + * CONCURRENTLY - but + * only on vacuums of user-defined tables. + */ + if (!(statusFlags & PROC_IN_REPACK)) + h->data_oldest_nonremovable = + TransactionIdOlder(h->data_oldest_nonremovable, xmin); + + /* Catalog tables need to consider all backends in this db */ + h->catalog_oldest_nonremovable = + TransactionIdOlder(h->catalog_oldest_nonremovable, xmin); + } } + /* catalog horizon should never be later than data */ + Assert(TransactionIdPrecedesOrEquals(h->catalog_oldest_nonremovable, + h->data_oldest_nonremovable)); + /* * If in recovery fetch oldest xid in KnownAssignedXids, will be applied * after lock is released. @@ -1835,6 +1853,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) TransactionIdOlder(h->shared_oldest_nonremovable, kaxmin); h->data_oldest_nonremovable = TransactionIdOlder(h->data_oldest_nonremovable, kaxmin); + h->catalog_oldest_nonremovable = + TransactionIdOlder(h->catalog_oldest_nonremovable, kaxmin); /* temp relations cannot be accessed in recovery */ } @@ -1862,7 +1882,9 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->shared_oldest_nonremovable = TransactionIdOlder(h->shared_oldest_nonremovable, h->slot_catalog_xmin); - h->catalog_oldest_nonremovable = h->data_oldest_nonremovable; + h->catalog_oldest_nonremovable = + TransactionIdOlder(h->catalog_oldest_nonremovable, + h->slot_xmin); h->catalog_oldest_nonremovable = TransactionIdOlder(h->catalog_oldest_nonremovable, h->slot_catalog_xmin); diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 039bc8353be..5396680d2b9 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -66,16 +66,18 @@ struct XidCache #define PROC_AFFECTS_ALL_HORIZONS 0x20 /* this proc's xmin must be * included in vacuum horizons * in all databases */ +#define PROC_IN_REPACK 0x40 + /* flags reset at EOXact */ #define PROC_VACUUM_STATE_MASK \ - (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND) + (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND | PROC_IN_REPACK) /* * Xmin-related flags. Make sure any flags that affect how the process' Xmin * value is interpreted by VACUUM are included here. */ -#define PROC_XMIN_FLAGS (PROC_IN_VACUUM | PROC_IN_SAFE_IC) +#define PROC_XMIN_FLAGS (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_IN_REPACK) /* * We allow a limited number of "weak" relation locks (AccessShareLock, -- 2.43.0