From: Andres Freund Date: Mon, 9 Jan 2023 10:23:10 -0800 Subject: [PATCH v2 1/3] Fix corruption due to vacuum_defer_cleanup_age underflowing 64bit xids Author: Reviewed-by: Discussion: https://postgr.es/m/20230108002923.cyoser3ttmt63bfn@awork3.anarazel.de Backpatch: --- src/backend/storage/ipc/procarray.c | 73 ++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 4340bf96416..d7d18ba8c12 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -367,6 +367,7 @@ static inline void ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId l static void ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid); static void MaintainLatestCompletedXid(TransactionId latestXid); static void MaintainLatestCompletedXidRecovery(TransactionId latestXid); +static int ClampVacuumDeferCleanupAge(FullTransactionId rel, TransactionId xid); static inline FullTransactionId FullXidRelativeTo(FullTransactionId rel, TransactionId xid); @@ -1888,17 +1889,32 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) * so guc.c should limit it to no more than the xidStopLimit threshold * in varsup.c. Also note that we intentionally don't apply * vacuum_defer_cleanup_age on standby servers. + * + * Be careful to clamp vacuum_defer_cleanup age to prevent it from + * creating an xid before FirstNormalTransactionId. */ - h->oldest_considered_running = - TransactionIdRetreatedBy(h->oldest_considered_running, - vacuum_defer_cleanup_age); - h->shared_oldest_nonremovable = - TransactionIdRetreatedBy(h->shared_oldest_nonremovable, - vacuum_defer_cleanup_age); - h->data_oldest_nonremovable = - TransactionIdRetreatedBy(h->data_oldest_nonremovable, - vacuum_defer_cleanup_age); - /* defer doesn't apply to temp relations */ + Assert(TransactionIdPrecedesOrEquals(h->oldest_considered_running, + h->shared_oldest_nonremovable)); + Assert(TransactionIdPrecedesOrEquals(h->shared_oldest_nonremovable, + h->data_oldest_nonremovable)); + + if (vacuum_defer_cleanup_age > 0) + { + int clamped_cleanup_age = + ClampVacuumDeferCleanupAge(h->latest_completed, + h->oldest_considered_running); + + h->oldest_considered_running = + TransactionIdRetreatedBy(h->oldest_considered_running, + clamped_cleanup_age); + h->shared_oldest_nonremovable = + TransactionIdRetreatedBy(h->shared_oldest_nonremovable, + clamped_cleanup_age); + h->data_oldest_nonremovable = + TransactionIdRetreatedBy(h->data_oldest_nonremovable, + clamped_cleanup_age); + /* defer doesn't apply to temp relations */ + } } /* @@ -2469,9 +2485,17 @@ GetSnapshotData(Snapshot snapshot) */ oldestfxid = FullXidRelativeTo(latest_completed, oldestxid); + def_vis_xid_data = xmin; + /* apply vacuum_defer_cleanup_age */ - def_vis_xid_data = - TransactionIdRetreatedBy(xmin, vacuum_defer_cleanup_age); + if (vacuum_defer_cleanup_age > 0) + { + int clamped_cleanup_age = + ClampVacuumDeferCleanupAge(oldestfxid, oldestxid); + + def_vis_xid_data = + TransactionIdRetreatedBy(xmin, clamped_cleanup_age); + } /* Check whether there's a replication slot requiring an older xmin. */ def_vis_xid_data = @@ -4295,6 +4319,31 @@ GlobalVisCheckRemovableXid(Relation rel, TransactionId xid) return GlobalVisTestIsRemovableXid(state, xid); } +/* + * Clamp vacuum_defer_cleanup_age, to prevent it from retreating below + * FirstNormalTransactionId during epoch 0. This is important to prevent + * generating xids that cannot be converted to a FullTransactionId without + * wrapping around. + */ +static int +ClampVacuumDeferCleanupAge(FullTransactionId rel, TransactionId oldest_xid) +{ + FullTransactionId oldest_fxid; + uint64 oldest_fxid_i; + + if (vacuum_defer_cleanup_age == 0) + return 0; + + oldest_fxid = FullXidRelativeTo(rel, oldest_xid); + oldest_fxid_i = U64FromFullTransactionId(oldest_fxid); + + if (oldest_fxid_i < vacuum_defer_cleanup_age || + (oldest_fxid_i - vacuum_defer_cleanup_age) < FirstNormalTransactionId) + return (int) (oldest_fxid_i - FirstNormalTransactionId); + + return vacuum_defer_cleanup_age; +} + /* * Convert a 32 bit transaction id into 64 bit transaction id, by assuming it * is within MaxTransactionId / 2 of XidFromFullTransactionId(rel). -- 2.38.0 --krsg47h7zl3qlzrm Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0002-wip-amcheck-Fix-bugs-relating-to-64bit-xids.patch"