From b2bd2dcb71fe017dd89d00293a19cd06275ccb26 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 29 Mar 2024 21:36:37 -0400
Subject: [PATCH v10 08/10] Set hastup in heap_page_prune

lazy_scan_prune() loops through the line pointers and tuple visibility
information for each tuple on a page, setting hastup to true if there
are any LP_REDIRECT line pointers or tuples with storage which will not
be removed. We want to remove this extra loop from lazy_scan_prune(),
and we know about non-removable tuples during heap_page_prune() anyway.
Set hastup when recording LP_REDIRECT line pointers in
heap_prune_chain() and when LP_NORMAL line pointers refer to tuples
whose visibility status is not HEAPTUPLE_DEAD.
---
 src/backend/access/heap/pruneheap.c  | 24 ++++++++++++++++++++++--
 src/backend/access/heap/vacuumlazy.c | 17 +----------------
 src/include/access/heapam.h          |  8 ++++++++
 3 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index be06699523..73fcc0081c 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -67,6 +67,9 @@ typedef struct
 	bool		marked[MaxHeapTuplesPerPage + 1];
 
 	int			ndeleted;		/* Number of tuples deleted from the page */
+
+	/* Whether or not the page makes rel truncation unsafe */
+	bool		hastup;
 } PruneState;
 
 /* Local functions */
@@ -273,6 +276,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 	prstate.ndeleted = 0;
 	prstate.nchain_members = 0;
 	prstate.nchain_candidates = 0;
+	prstate.hastup = false;
 
 	/*
 	 * If we will prepare to freeze tuples, consider that it might be possible
@@ -282,7 +286,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 		presult->all_frozen = true;
 	else
 		presult->all_frozen = false;
-
+	presult->hastup = prstate.hastup;
 
 	/*
 	 * presult->htsv is not initialized here because all ntuple spots in the
@@ -861,6 +865,8 @@ heap_prune_record_redirect(PruneState *prstate,
 	 */
 	if (was_normal)
 		prstate->ndeleted++;
+
+	prstate->hastup = true;
 }
 
 /* Record line pointer to be marked dead */
@@ -933,11 +939,15 @@ static void
 heap_prune_record_unchanged(Page page, int8 *htsv, PruneState *prstate,
 							PruneResult *presult, OffsetNumber offnum)
 {
-	HeapTupleHeader htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum));
+	HeapTupleHeader htup;
 
 	Assert(!prstate->marked[offnum]);
 	prstate->marked[offnum] = true;
 
+	presult->hastup = true;		/* the page is not empty */
+
+	htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum));
+
 	switch (htsv[offnum])
 	{
 		case HEAPTUPLE_LIVE:
@@ -1006,6 +1016,16 @@ heap_prune_record_unchanged_lp_dead(PruneState *prstate, OffsetNumber offnum)
 {
 	Assert(!prstate->marked[offnum]);
 	prstate->marked[offnum] = true;
+
+	/*
+	 * Deliberately don't set hastup for LP_DEAD items.  We make the soft
+	 * assumption that any LP_DEAD items encountered here will become
+	 * LP_UNUSED later on, before count_nondeletable_pages is reached.  If we
+	 * don't make this assumption then rel truncation will only happen every
+	 * other VACUUM, at most.  Besides, VACUUM must treat
+	 * hastup/nonempty_pages as provisional no matter how LP_DEAD items are
+	 * handled (handled here, or handled later on).
+	 */
 }
 
 
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 679c6a866e..212d76045e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1419,7 +1419,6 @@ lazy_scan_prune(LVRelState *vacrel,
 	int			lpdead_items,
 				live_tuples,
 				recently_dead_tuples;
-	bool		hastup = false;
 	bool		all_visible;
 	TransactionId visibility_cutoff_xid;
 	uint8		actions = 0;
@@ -1500,23 +1499,11 @@ lazy_scan_prune(LVRelState *vacrel,
 
 		/* Redirect items mustn't be touched */
 		if (ItemIdIsRedirected(itemid))
-		{
-			/* page makes rel truncation unsafe */
-			hastup = true;
 			continue;
-		}
 
 		if (ItemIdIsDead(itemid))
 		{
 			/*
-			 * Deliberately don't set hastup for LP_DEAD items.  We make the
-			 * soft assumption that any LP_DEAD items encountered here will
-			 * become LP_UNUSED later on, before count_nondeletable_pages is
-			 * reached.  If we don't make this assumption then rel truncation
-			 * will only happen every other VACUUM, at most.  Besides, VACUUM
-			 * must treat hastup/nonempty_pages as provisional no matter how
-			 * LP_DEAD items are handled (handled here, or handled later on).
-			 *
 			 * Also deliberately delay unsetting all_visible until just before
 			 * we return to lazy_scan_heap caller, as explained in full below.
 			 * (This is another case where it's useful to anticipate that any
@@ -1631,8 +1618,6 @@ lazy_scan_prune(LVRelState *vacrel,
 				elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
 				break;
 		}
-
-		hastup = true;			/* page makes rel truncation unsafe */
 	}
 
 	/*
@@ -1786,7 +1771,7 @@ lazy_scan_prune(LVRelState *vacrel,
 	vacrel->recently_dead_tuples += recently_dead_tuples;
 
 	/* Can't truncate this page */
-	if (hastup)
+	if (presult.hastup)
 		vacrel->nonempty_pages = blkno + 1;
 
 	/* Did we find LP_DEAD items? */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index a7f5f19916..2311d01998 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -231,6 +231,14 @@ typedef struct PruneResult
 	 */
 	int8		htsv[MaxHeapTuplesPerPage + 1];
 
+	/*
+	 * Whether or not the page makes rel truncation unsafe
+	 *
+	 * This is set to 'true', even if the page contains LP_DEAD items. VACUUM
+	 * will remove them before attempting to truncate.
+	 */
+	bool		hastup;
+
 	/*
 	 * Prepare to freeze in heap_page_prune(). lazy_scan_prune() will use the
 	 * returned freeze plans to execute freezing.
-- 
2.40.1

