On Fri, Sep 18, 2026 at 11:23:37AM -0700, Bharath Rupireddy wrote:
> On Thu, Sep 17, 2026 at 6:00 PM shihao zhong <[email protected]> wrote:
>> v7 looks good here. Tested sequential, parallel on btree and GIN, all as the
>> docs describe, including GIN reporting zero blocks and the reset
>> clearing the previous index. Applies cleanly on 5e595d659fd, suites
>> pass.
> 
> Thanks for testing v7 on these and for confirming the behavior matches the 
> docs.

I've looked at v7, and my main comment is that this is bloated in
terms of docs and comments.  My hands-on is leading me to the attached
result, that reduces the docs to actually explain what these new
counters do, and only that, including your note at the bottom about
the workers and the relevant fields.  :D

  * Since parallel vacuum workers perform only index vacuum or index cleanup,
- * we don't need to report progress information.
+ * they report progress for the index they are processing.

This reads a bit weird as well..

Just for transparency, I've used the following thing to check the
state of the progress.  One session to create and destroy:
CREATE TABLE pvac (a int, b int, c int, d int[]);
INSERT INTO pvac
  SELECT i, i, i, ARRAY[i, i+1, i+2] FROM generate_series(1, 3000000) i;
CREATE INDEX pvac_a ON pvac (a);
CREATE INDEX pvac_b ON pvac (b);
CREATE INDEX pvac_c ON pvac (c);
CREATE INDEX pvac_d ON pvac USING gin (d);
DELETE FROM pvac WHERE a % 3 = 0;
SET maintenance_work_mem = '1MB';
SET max_parallel_maintenance_workers = 4;
VACUUM (PARALLEL 4, VERBOSE) pvac;

And a second session to monitor (lower \watch for more output):
SELECT a.pid, a.leader_pid, v.phase,
       v.current_index_relid::regclass AS idx,
       v.index_blks_done, v.index_blks_total,
       v.indexes_processed, v.indexes_total,
       v.heap_blks_scanned, v.heap_blks_total
  FROM pg_stat_progress_vacuum v
  JOIN pg_stat_activity a USING (pid)
 WHERE v.relid = 'pvac'::regclass
 ORDER BY a.leader_pid NULLS FIRST, a.pid \watch 0.5

And I'd say that this is pretty nice to see all this information.
Nice result for a limited amount of code added.

Then, I don't really have a lot of feelings for v7-0002.  Even if all
the paths set the progress flag to true in the backend core code,
I think that there is an out-of-core argument in favor of keeping it,
as some code out there may want to control if progress should show up
or not.  And I suspect that we will need it at some point..
--
Michael
From a6583869eb884c02fc48f51074668379296fd77b Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Thu, 1 Oct 2026 10:39:53 +0900
Subject: [PATCH v8] Report per-index vacuum progress in
 pg_stat_progress_vacuum

Blah.

Bump catalog version.

Author: Bharath Rupireddy <[email protected]>
Reviewed-by: Sami Imseih <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Masahiko Sawada <[email protected]>
Discussion: 
https://postgr.es/m/calj2acugwschk6jq2cdklbwuadtoe_zkdtff2zg3e6houxk...@mail.gmail.com
---
 src/include/commands/progress.h       |  2 +
 src/backend/access/heap/vacuumlazy.c  | 28 ++++++++++++-
 src/backend/catalog/system_views.sql  |  5 ++-
 src/backend/commands/vacuumparallel.c | 31 +++++++++++++--
 src/test/regress/expected/rules.out   |  5 ++-
 doc/src/sgml/monitoring.sgml          | 57 ++++++++++++++++++++++++++-
 6 files changed, 119 insertions(+), 9 deletions(-)

diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index 2a12920c75f2..bf91455eff94 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -31,6 +31,8 @@
 #define PROGRESS_VACUUM_DELAY_TIME                             10
 #define PROGRESS_VACUUM_MODE                                   11
 #define PROGRESS_VACUUM_STARTED_BY                             12
+#define PROGRESS_VACUUM_CURRENT_INDEX_RELID            13
+/* 15 and 16 reserved for "block number" metrics */
 
 /* Phases of vacuum (as advertised via PROGRESS_VACUUM_PHASE) */
 #define PROGRESS_VACUUM_PHASE_SCAN_HEAP                        1
diff --git a/src/backend/access/heap/vacuumlazy.c 
b/src/backend/access/heap/vacuumlazy.c
index 997d84a77b35..1f684b81f827 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -3038,17 +3038,27 @@ 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.is_autovacuum = AmAutoVacuumWorkerProcess();
-       ivinfo.report_progress = false;
+       ivinfo.report_progress = true;
        ivinfo.estimated_count = true;
        ivinfo.message_level = DEBUG2;
        ivinfo.num_heap_tuples = reltuples;
        ivinfo.strategy = vacrel->bstrategy;
 
+       /* Report which index we're currently processing */
+       pgstat_progress_update_param(PROGRESS_VACUUM_CURRENT_INDEX_RELID,
+                                                                (int64) 
RelationGetRelid(indrel));
+
        /*
         * Update error traceback information.
         *
@@ -3070,6 +3080,8 @@ lazy_vacuum_one_index(Relation indrel, 
IndexBulkDeleteResult *istat,
        pfree(vacrel->indname);
        vacrel->indname = NULL;
 
+       pgstat_progress_update_multi_param(3, reset_index, reset_val);
+
        return istat;
 }
 
@@ -3089,18 +3101,28 @@ 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.is_autovacuum = AmAutoVacuumWorkerProcess();
-       ivinfo.report_progress = false;
+       ivinfo.report_progress = true;
        ivinfo.estimated_count = estimated_count;
        ivinfo.message_level = DEBUG2;
 
        ivinfo.num_heap_tuples = reltuples;
        ivinfo.strategy = vacrel->bstrategy;
 
+       /* Report which index we're currently processing */
+       pgstat_progress_update_param(PROGRESS_VACUUM_CURRENT_INDEX_RELID,
+                                                                (int64) 
RelationGetRelid(indrel));
+
        /*
         * Update error traceback information.
         *
@@ -3120,6 +3142,8 @@ lazy_cleanup_one_index(Relation indrel, 
IndexBulkDeleteResult *istat,
        pfree(vacrel->indname);
        vacrel->indname = NULL;
 
+       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 ad340887f541..809b9c0f1e48 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1353,7 +1353,10 @@ CREATE VIEW pg_stat_progress_vacuum AS
         CASE S.param13 WHEN 1 THEN 'manual'
                        WHEN 2 THEN 'autovacuum'
                        WHEN 3 THEN 'autovacuum_wraparound'
-                       ELSE NULL END AS started_by
+                       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
     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 d4572861000f..301cad428925 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1080,6 +1080,17 @@ parallel_vacuum_process_one_index(ParallelVacuumState 
*pvs, Relation indrel,
        IndexBulkDeleteResult *istat = NULL;
        IndexBulkDeleteResult *istat_res;
        IndexVacuumInfo ivinfo;
+       const int       progress_index[] = {
+               PROGRESS_VACUUM_PHASE,
+               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 +1103,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState 
*pvs, Relation indrel,
        ivinfo.heaprel = pvs->heaprel;
        ivinfo.analyze_only = false;
        ivinfo.is_autovacuum = pvs->shared->is_autovacuum;
-       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;
@@ -1102,6 +1113,13 @@ parallel_vacuum_process_one_index(ParallelVacuumState 
*pvs, Relation indrel,
        pvs->indname = pstrdup(RelationGetRelationName(indrel));
        pvs->status = indstats->status;
 
+       /* Report the phase and the index we're about to process */
+       progress_val[0] = (indstats->status == 
PARALLEL_INDVAC_STATUS_NEED_BULKDELETE)
+               ? PROGRESS_VACUUM_PHASE_VACUUM_INDEX
+               : PROGRESS_VACUUM_PHASE_INDEX_CLEANUP;
+       progress_val[1] = (int64) RelationGetRelid(indrel);
+       pgstat_progress_update_multi_param(2, progress_index, progress_val);
+
        switch (indstats->status)
        {
                case PARALLEL_INDVAC_STATUS_NEED_BULKDELETE:
@@ -1117,6 +1135,8 @@ parallel_vacuum_process_one_index(ParallelVacuumState 
*pvs, Relation indrel,
                                 RelationGetRelationName(indrel));
        }
 
+       pgstat_progress_update_multi_param(3, reset_index, reset_val);
+
        /*
         * Copy the index bulk-deletion result returned from ambulkdelete and
         * amvacuumcleanup to the DSM segment if it's the first cycle because 
they
@@ -1195,8 +1215,8 @@ parallel_vacuum_index_is_parallel_safe(Relation indrel, 
int num_index_scans,
 /*
  * Perform work within a launched parallel process.
  *
- * Since parallel vacuum workers perform only index vacuum or index cleanup,
- * we don't need to report progress information.
+ * Parallel vacuum workers perform only index vacuum or index cleanup; they
+ * report progress for the index they are processing.
  */
 void
 parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
@@ -1320,6 +1340,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
        /* Prepare to track buffer usage during parallel execution */
        InstrStartParallelQuery();
 
+       /* Register this worker for vacuum progress reporting */
+       pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM, shared->relid);
+
        /* Process indexes to perform vacuum/cleanup */
        parallel_vacuum_process_safe_indexes(&pvs);
 
@@ -1339,6 +1362,8 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
        /* Pop the error context stack */
        error_context_stack = errcallback.previous;
 
+       pgstat_progress_end_command();
+
        vac_close_indexes(nindexes, indrels, RowExclusiveLock);
        table_close(rel, ShareUpdateExclusiveLock);
        FreeAccessStrategy(pvs.bstrategy);
diff --git a/src/test/regress/expected/rules.out 
b/src/test/regress/expected/rules.out
index 4a8cc759d7b2..0addd043e68d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2214,7 +2214,10 @@ pg_stat_progress_vacuum| SELECT s.pid,
             WHEN 2 THEN 'autovacuum'::text
             WHEN 3 THEN 'autovacuum_wraparound'::text
             ELSE NULL::text
-        END AS started_by
+        END AS started_by,
+    (s.param14)::oid AS current_index_relid,
+    s.param16 AS index_blks_total,
+    s.param17 AS index_blks_done
    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)));
 pg_stat_recovery| SELECT promote_triggered,
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 0d038de1a238..b6d6f8e801c3 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -7685,8 +7685,10 @@ FROM pg_stat_get_backend_idset() AS backendid;
   <para>
    Whenever <command>VACUUM</command> is running, the
    <structname>pg_stat_progress_vacuum</structname> view will contain
-   one row for each backend (including autovacuum worker processes) that is
-   currently vacuuming.  The tables below describe the information
+   one row for each backend (including autovacuum worker processes and
+   parallel workers launched for
+   <link linkend="sql-vacuum">parallel vacuum</link>) that is currently
+   vacuuming.  The tables below describe the information
    that will be reported and provide information about how to interpret it.
    Progress for <command>VACUUM FULL</command> commands is reported via
    <structname>pg_stat_progress_repack</structname>, and is also visible via
@@ -7944,10 +7946,61 @@ FROM pg_stat_get_backend_idset() AS backendid;
        </itemizedlist>
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>current_index_relid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of the index currently being vacuumed, or 0 if none is.  This
+       field is only valid when the phase is
+       <literal>vacuuming indexes</literal> or
+       <literal>cleaning up indexes</literal>.
+      </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 in the index identified by
+       <structfield>current_index_relid</structfield>.  Only B-tree indexes
+       report this; it is 0 for other index access methods, and when no index
+       scan is in progress.
+      </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 of that index scanned so far.  It stops one block
+       short of <structfield>index_blks_total</structfield>, which counts the
+       index metapage, never scanned.
+      </para></entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
 
+  <note>
+   <para>
+    During a parallel vacuum, the leader and each of its workers reports its
+    own row, all sharing the same <structfield>relid</structfield>.  Only
+    <structfield>phase</structfield>,
+    <structfield>current_index_relid</structfield>,
+    <structfield>index_blks_total</structfield> and
+    <structfield>index_blks_done</structfield> are maintained on a worker row;
+    the remaining columns track command-level progress that only the leader
+    maintains.  Use <structfield>leader_pid</structfield> in
+    <link 
linkend="monitoring-pg-stat-activity-view"><structname>pg_stat_activity</structname></link>
+    to tell the leader row from its worker rows.
+   </para>
+  </note>
+
   <table id="vacuum-phases">
    <title>VACUUM Phases</title>
    <tgroup cols="2">
-- 
2.55.0

Attachment: signature.asc
Description: PGP signature

Reply via email to