From 4cd92806c6e6a38a9913b9c37c85e498071c1dca Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Tue, 25 Aug 2026 03:20:56 +0000
Subject: [PATCH v4 2/2] Report index block progress in
 pg_stat_progress_vacuum.

Previously, pg_stat_progress_vacuum reported which index a
backend was vacuuming but not how far it had gotten through that
index. This matters most for B-tree, the common index type: for a
large B-tree, at the scale of hundreds of GBs to TBs, per-index
progress is what lets one estimate when the index phase, and
together with heap_blks_* the whole vacuum, will finish.

This commit reuses the block counters that CREATE INDEX progress
reporting added in commit ab0dfc961 (PROGRESS_SCAN_BLOCKS_TOTAL
and PROGRESS_SCAN_BLOCKS_DONE), exposing them as new
index_blks_total and index_blks_done columns. B-tree's
bulk-delete scan already knows how to report them, so this commit
only turns that reporting on in the serial and parallel
index-vacuum paths. Like current_index_relid, the counters are
reported per participating backend, so each worker's row shows
progress for the index it is scanning.

These counters are kept separate from heap_blks_*. The heap block
counters must be retained across a multi-pass index vacuum, which
happens when the dead-TID store fills, and in the serial case the
leader does the index vacuuming itself, so reusing the heap
counters for index blocks would corrupt heap progress that is
still needed.

No caller sets IndexVacuumInfo.report_progress to false anymore;
removing it is parked for a follow-up commit.

Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CALj2ACX6gyBmQfaqbCsycDmaSPbq1=iPJw1OqUT+aLqaKjW8dQ@mail.gmail.com
---
 doc/src/sgml/monitoring.sgml          | 27 +++++++++++++++++++++-
 src/backend/access/heap/vacuumlazy.c  | 32 ++++++++++++++++++++-------
 src/backend/catalog/system_views.sql  |  2 ++
 src/backend/commands/vacuumparallel.c | 16 ++++++++++----
 src/test/regress/expected/rules.out   |  2 ++
 5 files changed, 66 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 0d3446d6e86..0f5dc69a246 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -7929,6 +7929,29 @@ FROM pg_stat_get_backend_idset() AS backendid;
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>index_blks_total</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Total number of blocks to scan for the B-tree index identified by
+       <structfield>current_index_relid</structfield>. It is reset to 0 once
+       the index has been processed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>index_blks_done</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of blocks scanned so far for the B-tree index identified by
+       <structfield>current_index_relid</structfield>. Together with
+       <structfield>index_blks_total</structfield> this gives per-index vacuum
+       progress, which is useful for estimating completion of large indexes.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>leader_pid</structfield> <type>integer</type>
@@ -7952,7 +7975,9 @@ FROM pg_stat_get_backend_idset() AS backendid;
     During a parallel vacuum, each participating backend (the leader and each
     parallel worker) reports its own row. Only a subset of the columns is
     meaningful on a worker row: <structfield>phase</structfield>,
-    <structfield>current_index_relid</structfield> and
+    <structfield>current_index_relid</structfield>,
+    <structfield>index_blks_total</structfield>,
+    <structfield>index_blks_done</structfield> and
     <structfield>leader_pid</structfield>. The remaining columns track
     command-level heap progress and are maintained only on the leader row;
     they read as zero on worker rows. <structfield>leader_pid</structfield> is
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8bc3de84581..459fff8df1d 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -3038,11 +3038,17 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 {
 	IndexVacuumInfo ivinfo;
 	LVSavedErrInfo saved_err_info;
+	const int	reset_index[] = {
+		PROGRESS_VACUUM_CURRENT_INDEX_RELID,
+		PROGRESS_SCAN_BLOCKS_TOTAL,
+		PROGRESS_SCAN_BLOCKS_DONE
+	};
+	const int64 reset_val[] = {(int64) InvalidOid, 0, 0};
 
 	ivinfo.index = indrel;
 	ivinfo.heaprel = vacrel->rel;
 	ivinfo.analyze_only = false;
-	ivinfo.report_progress = false;
+	ivinfo.report_progress = true;
 	ivinfo.estimated_count = true;
 	ivinfo.message_level = DEBUG2;
 	ivinfo.num_heap_tuples = reltuples;
@@ -3073,9 +3079,11 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	pfree(vacrel->indname);
 	vacrel->indname = NULL;
 
-	/* Reset the current index relid to avoid reporting a stale value */
-	pgstat_progress_update_param(PROGRESS_VACUUM_CURRENT_INDEX_RELID,
-								 (int64) InvalidOid);
+	/*
+	 * Reset the current index progress parameters to avoid reporting stale
+	 * values.
+	 */
+	pgstat_progress_update_multi_param(3, reset_index, reset_val);
 
 	return istat;
 }
@@ -3096,11 +3104,17 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 {
 	IndexVacuumInfo ivinfo;
 	LVSavedErrInfo saved_err_info;
+	const int	reset_index[] = {
+		PROGRESS_VACUUM_CURRENT_INDEX_RELID,
+		PROGRESS_SCAN_BLOCKS_TOTAL,
+		PROGRESS_SCAN_BLOCKS_DONE
+	};
+	const int64 reset_val[] = {(int64) InvalidOid, 0, 0};
 
 	ivinfo.index = indrel;
 	ivinfo.heaprel = vacrel->rel;
 	ivinfo.analyze_only = false;
-	ivinfo.report_progress = false;
+	ivinfo.report_progress = true;
 	ivinfo.estimated_count = estimated_count;
 	ivinfo.message_level = DEBUG2;
 
@@ -3130,9 +3144,11 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	pfree(vacrel->indname);
 	vacrel->indname = NULL;
 
-	/* Reset the current index relid to avoid reporting a stale value */
-	pgstat_progress_update_param(PROGRESS_VACUUM_CURRENT_INDEX_RELID,
-								 (int64) InvalidOid);
+	/*
+	 * Reset the current index progress parameters to avoid reporting stale
+	 * values.
+	 */
+	pgstat_progress_update_multi_param(3, reset_index, reset_val);
 
 	return istat;
 }
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 64a627b37b2..741e8f4fa36 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1355,6 +1355,8 @@ CREATE VIEW pg_stat_progress_vacuum AS
                        WHEN 3 THEN 'autovacuum_wraparound'
                        ELSE NULL END AS started_by,
         CAST(S.param14 AS oid) AS current_index_relid,
+        S.param16 AS index_blks_total,
+        S.param17 AS index_blks_done,
         A.leader_pid AS leader_pid
     FROM pg_stat_get_progress_info('VACUUM') AS S
         LEFT JOIN pg_database D ON S.datid = D.oid
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index d6a56f63a0f..be87b1937ad 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1081,6 +1081,12 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
 		PROGRESS_VACUUM_CURRENT_INDEX_RELID
 	};
 	int64		progress_val[2];
+	const int	reset_index[] = {
+		PROGRESS_VACUUM_CURRENT_INDEX_RELID,
+		PROGRESS_SCAN_BLOCKS_TOTAL,
+		PROGRESS_SCAN_BLOCKS_DONE
+	};
+	const int64 reset_val[] = {(int64) InvalidOid, 0, 0};
 
 	/*
 	 * Update the pointer to the corresponding bulk-deletion result if someone
@@ -1092,7 +1098,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
 	ivinfo.index = indrel;
 	ivinfo.heaprel = pvs->heaprel;
 	ivinfo.analyze_only = false;
-	ivinfo.report_progress = false;
+	ivinfo.report_progress = true;
 	ivinfo.message_level = DEBUG2;
 	ivinfo.estimated_count = pvs->shared->estimated_count;
 	ivinfo.num_heap_tuples = pvs->shared->reltuples;
@@ -1127,9 +1133,11 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
 				 RelationGetRelationName(indrel));
 	}
 
-	/* Reset the current index relid to avoid reporting a stale value */
-	pgstat_progress_update_param(PROGRESS_VACUUM_CURRENT_INDEX_RELID,
-								 (int64) InvalidOid);
+	/*
+	 * Reset the current index progress parameters to avoid reporting stale
+	 * values.
+	 */
+	pgstat_progress_update_multi_param(3, reset_index, reset_val);
 
 	/*
 	 * Copy the index bulk-deletion result returned from ambulkdelete and
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 17caf9418f2..c5ad0881611 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2215,6 +2215,8 @@ pg_stat_progress_vacuum| SELECT s.pid,
             ELSE NULL::text
         END AS started_by,
     (s.param14)::oid AS current_index_relid,
+    s.param16 AS index_blks_total,
+    s.param17 AS index_blks_done,
     a.leader_pid
    FROM ((pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)))
-- 
2.47.3

