From a42ebb4ea9847607106aa6a1f34b58882f6edc1e Mon Sep 17 00:00:00 2001
From: TatsuyaKawata <kawatatatsuya0913@gmail.com>
Date: Thu, 20 Nov 2025 22:46:57 +0900
Subject: [PATCH v4] Add memory usage reporting to VACUUM VERBOSE

---
 src/backend/access/heap/vacuumlazy.c | 29 ++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index deb9a3dc0d1..14f6f1ec1cf 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -347,6 +347,8 @@ typedef struct LVRelState
 
 	/* Instrumentation counters */
 	int			num_index_scans;
+	int			num_dead_items_resets;	/* # times dead_items was reset */
+	Size		total_dead_items_bytes;	/* # total memory usage for dead_items */
 	/* Counters that follow are only for scanned_pages */
 	int64		tuples_deleted; /* # deleted from table */
 	int64		tuples_frozen;	/* # newly frozen */
@@ -645,6 +647,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 	BufferUsage startbufferusage = pgBufferUsage;
 	ErrorContextCallback errcallback;
 	char	  **indnames = NULL;
+	Size		dead_items_max_bytes;	/* # save max_bytes before cleanup */
 
 	verbose = (params.options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -765,11 +768,14 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 	vacrel->live_tuples = 0;
 	vacrel->recently_dead_tuples = 0;
 	vacrel->missed_dead_tuples = 0;
+	vacrel->total_dead_items_bytes = 0;
+	vacrel->num_dead_items_resets = 0;
 
 	vacrel->vm_new_visible_pages = 0;
 	vacrel->vm_new_visible_frozen_pages = 0;
 	vacrel->vm_new_frozen_pages = 0;
 
+	dead_items_max_bytes = 0;
 	/*
 	 * Get cutoffs that determine which deleted tuples are considered DEAD,
 	 * not just RECENTLY_DEAD, and which XIDs/MXIDs to freeze.  Then determine
@@ -844,6 +850,12 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 	lazy_check_wraparound_failsafe(vacrel);
 	dead_items_alloc(vacrel, params.nworkers);
 
+	/*
+	 * Save max_bytes before cleanup, as dead_items_info may be freed in
+	 * parallel mode during dead_items_cleanup().
+	 */
+	dead_items_max_bytes = vacrel->dead_items_info->max_bytes;
+
 	/*
 	 * Call lazy_scan_heap to perform all required heap pruning, index
 	 * vacuuming, and heap vacuuming (plus related processing)
@@ -1154,6 +1166,14 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 							 walusage.wal_bytes,
 							 walusage.wal_fpi_bytes,
 							 walusage.wal_buffers_full);
+			/*
+			 * Report memory usage for dead_items tracking.
+			 */
+			appendStringInfo(&buf,
+							_("memory usage: total %.2f MB used across %d index scan(s) (max mem space is limited %.2f MB at once)\n"),
+							(double) vacrel->total_dead_items_bytes / (1024.0 * 1024.0),
+							vacrel->num_dead_items_resets,
+							(double) dead_items_max_bytes / (1024.0 * 1024.0));
 			appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
 
 			ereport(verbose ? INFO : LOG,
@@ -3598,6 +3618,15 @@ dead_items_add(LVRelState *vacrel, BlockNumber blkno, OffsetNumber *offsets,
 static void
 dead_items_reset(LVRelState *vacrel)
 {
+	/* Increment dead_items reset counter */
+	vacrel->num_dead_items_resets++;
+
+	/* Track total memory usage for dead_items */
+	if (vacrel->dead_items != NULL)
+	{
+		vacrel->total_dead_items_bytes += TidStoreMemoryUsage(vacrel->dead_items);
+	}
+
 	if (ParallelVacuumIsActive(vacrel))
 	{
 		parallel_vacuum_reset_dead_items(vacrel->pvs);
-- 
2.34.1

