From 0328cad69ef7504ef7876c46a834be06973c7ca7 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Fri, 12 Nov 2021 12:30:10 -0800
Subject: [PATCH v1] Clear up ndeleted pgstats accounting.

---
 src/include/access/heapam.h          |  2 +-
 src/backend/access/heap/pruneheap.c  | 45 +++++++++++++++++-----------
 src/backend/access/heap/vacuumlazy.c |  3 +-
 3 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index e63b49fc3..417dd288e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -186,7 +186,7 @@ extern int	heap_page_prune(Relation relation, Buffer buffer,
 							struct GlobalVisState *vistest,
 							TransactionId old_snap_xmin,
 							TimestampTz old_snap_ts_ts,
-							bool report_stats,
+							int	*nnewlpdead,
 							OffsetNumber *off_loc);
 extern void heap_page_prune_execute(Buffer buffer,
 									OffsetNumber *redirected, int nredirected,
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 50ed76198..58991696b 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -182,10 +182,29 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
 		 */
 		if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
 		{
-			/* OK to prune */
-			(void) heap_page_prune(relation, buffer, vistest,
-								   limited_xmin, limited_ts,
-								   true, NULL);
+			int		nnewlpdead,
+					ndeleted;
+
+			ndeleted = heap_page_prune(relation, buffer, vistest, limited_xmin,
+									   limited_ts, &nnewlpdead, NULL);
+
+			/*
+			 * Report the number of tuples reclaimed to pgstats.  This is
+			 * ndeleted minus the number of newly-LP_DEAD-set items.
+			 *
+			 * We derive the number of dead tuples we have removed like this
+			 * because we don't want to totally forget about items that were
+			 * set to LP_DEAD, just because they no longer have tuple storage.
+			 * We only want to count heap-only tuples that just became
+			 * LP_UNUSED in our report.
+			 *
+			 * VACUUM doesn't have to compensate in the same way when it
+			 * consumes ndeleted, since it will set the LP_DEAD items to
+			 * LP_UNUSED in the near future anyway (after index vacuuming).
+			 */
+			if (ndeleted > nnewlpdead)
+				pgstat_update_heap_dead_tuples(relation,
+											   ndeleted - nnewlpdead);
 		}
 
 		/* And release buffer lock */
@@ -212,10 +231,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
  * 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
- * send its own new total to pgstats, and we don't want this delta applied
- * on top of that.)
+ * Sets *nnewlpdead for caller, indicating the number of items that were newly
+ * set to LP_DEAD during the prune.
  *
  * off_loc is the offset location required by the caller to use in error
  * callback.
@@ -227,7 +244,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 				GlobalVisState *vistest,
 				TransactionId old_snap_xmin,
 				TimestampTz old_snap_ts,
-				bool report_stats,
+				int	*nnewlpdead,
 				OffsetNumber *off_loc)
 {
 	int			ndeleted = 0;
@@ -285,6 +302,8 @@ heap_page_prune(Relation relation, Buffer buffer,
 		ndeleted += heap_prune_chain(buffer, offnum, &prstate);
 	}
 
+	/* Set number of newly-set-LP_DEAD items for caller */
+	*nnewlpdead = prstate.ndead;
 	/* Clear the offset information once we have processed the given page. */
 	if (off_loc)
 		*off_loc = InvalidOffsetNumber;
@@ -381,14 +400,6 @@ heap_page_prune(Relation relation, Buffer buffer,
 
 	END_CRIT_SECTION();
 
-	/*
-	 * If requested, report the number of tuples reclaimed to pgstats. This is
-	 * ndeleted minus ndead, because we don't want to count a now-DEAD root
-	 * item as a deletion for this purpose.
-	 */
-	if (report_stats && ndeleted > prstate.ndead)
-		pgstat_update_heap_dead_tuples(relation, ndeleted - prstate.ndead);
-
 	return ndeleted;
 }
 
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1dd162ab9..4ee1f1485 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1712,6 +1712,7 @@ lazy_scan_prune(LVRelState *vacrel,
 				new_dead_tuples,
 				num_tuples,
 				live_tuples;
+	int			nnewlpdead;
 	int			nfrozen;
 	OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
 	xl_heap_freeze_tuple frozen[MaxHeapTuplesPerPage];
@@ -1737,7 +1738,7 @@ retry:
 	 * that were deleted from indexes.
 	 */
 	tuples_deleted = heap_page_prune(rel, buf, vistest,
-									 InvalidTransactionId, 0, false,
+									 InvalidTransactionId, 0, &nnewlpdead,
 									 &vacrel->offnum);
 
 	/*
-- 
2.30.2

