v1-0001-Fix-vacuum-hang.patch

text/x-patch

Filename: v1-0001-Fix-vacuum-hang.patch
Type: text/x-patch
Part: 0
Message: Re: relfrozenxid may disagree with row XIDs after 1ccc1e05ae

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v1-0001
Subject: Fix vacuum hang
File+
src/backend/access/heap/pruneheap.c 24 9
src/backend/access/heap/vacuumlazy.c 1 1
src/include/access/heapam.h 1 0
From 33ec54d05441183ba908a728186b2a6751bc4cf5 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Mon, 13 May 2024 10:27:50 -0400
Subject: [PATCH v1] Fix vacuum hang

---
 src/backend/access/heap/pruneheap.c  | 33 ++++++++++++++++++++--------
 src/backend/access/heap/vacuumlazy.c |  2 +-
 src/include/access/heapam.h          |  1 +
 3 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index f7f8056d631..0356391c5db 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -35,6 +35,12 @@ typedef struct
 	/* tuple visibility test, initialized for the relation */
 	GlobalVisState *vistest;
 
+	/*
+	 * Used only by vacuum. The oldest xmin at the beginning of vacuuming the
+	 * relation.
+	 */
+	TransactionId oldest_xmin;
+
 	/*
 	 * Thresholds set by TransactionIdLimitedForOldSnapshots() if they have
 	 * been computed (done on demand, and only if
@@ -203,6 +209,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
 		{
 			/* OK to prune */
 			(void) heap_page_prune(relation, buffer, vistest,
+								   InvalidTransactionId,
 								   limited_xmin, limited_ts,
 								   true, NULL);
 		}
@@ -218,11 +225,14 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
  *
  * Caller must have pin and buffer cleanup lock on the page.
  *
- * vistest is used to distinguish whether tuples are DEAD or RECENTLY_DEAD
- * (see heap_prune_satisfies_vacuum and
- * HeapTupleSatisfiesVacuum). old_snap_xmin / old_snap_ts need to
- * either have been set by TransactionIdLimitedForOldSnapshots, or
- * InvalidTransactionId/0 respectively.
+ * vistest is used to distinguish whether tuples are DEAD or RECENTLY_DEAD (see
+ * heap_prune_satisfies_vacuum and HeapTupleSatisfiesVacuum).
+ *
+ * oldest_xmin is only set to a valid TransactionId by vacuum. It is the oldest
+ * removable TransactionId at the beginning of vacuuming the relation.
+ *
+ * old_snap_xmin / old_snap_ts need to either have been set by
+ * TransactionIdLimitedForOldSnapshots, or InvalidTransactionId/0 respectively.
  *
  * If report_stats is true then we send the number of reclaimed heap-only
  * tuples to pgstats.  (This must be false during vacuum, since vacuum will
@@ -237,6 +247,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
 int
 heap_page_prune(Relation relation, Buffer buffer,
 				GlobalVisState *vistest,
+				TransactionId oldest_xmin,
 				TransactionId old_snap_xmin,
 				TimestampTz old_snap_ts,
 				bool report_stats,
@@ -263,6 +274,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 	prstate.new_prune_xid = InvalidTransactionId;
 	prstate.rel = relation;
 	prstate.vistest = vistest;
+	prstate.oldest_xmin = oldest_xmin;
 	prstate.old_snap_xmin = old_snap_xmin;
 	prstate.old_snap_ts = old_snap_ts;
 	prstate.old_snap_used = false;
@@ -512,11 +524,14 @@ heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer)
 	}
 
 	/*
-	 * First check if GlobalVisTestIsRemovableXid() is sufficient to find the
-	 * row dead. If not, and old_snapshot_threshold is enabled, try to use the
-	 * lowered horizon.
+	 * First check if oldest_xmin or GlobalVisTestIsRemovableXid() is
+	 * sufficient to find the row dead. If not, and old_snapshot_threshold is
+	 * enabled, try to use the lowered horizon.
 	 */
-	if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after))
+	if (TransactionIdIsValid(prstate->oldest_xmin) &&
+			NormalTransactionIdPrecedes(dead_after, prstate->oldest_xmin))
+			res = HEAPTUPLE_DEAD;
+	else if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after))
 		res = HEAPTUPLE_DEAD;
 	else if (OldSnapshotThresholdActive())
 	{
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8aab6e324e0..31fdc8e6cd9 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1738,7 +1738,7 @@ retry:
 	 * lpdead_items's final value can be thought of as the number of tuples
 	 * that were deleted from indexes.
 	 */
-	tuples_deleted = heap_page_prune(rel, buf, vistest,
+	tuples_deleted = heap_page_prune(rel, buf, vistest, vacrel->OldestXmin,
 									 InvalidTransactionId, 0, false,
 									 &vacrel->offnum);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 4f1dff9ca1b..780b30fe74c 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -187,6 +187,7 @@ struct GlobalVisState;
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
 extern int	heap_page_prune(Relation relation, Buffer buffer,
 							struct GlobalVisState *vistest,
+							TransactionId oldest_xmin,
 							TransactionId old_snap_xmin,
 							TimestampTz old_snap_ts_ts,
 							bool report_stats,
-- 
2.34.1