0007-Extended-vacuum-statistics-total-shared-buffer-acces.patch

text/plain

Filename: 0007-Extended-vacuum-statistics-total-shared-buffer-acces.patch
Type: text/plain
Part: 6
Message: Re: Vacuum statistics

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0007
Subject: Extended vacuum statistics: total shared-buffer access counters
File+
doc/src/sgml/system-views.sgml 96 0
src/backend/access/heap/vacuumlazy.c 22 1
src/backend/catalog/system_views.sql 12 0
src/backend/utils/activity/pgstat_vacuum.c 5 0
src/backend/utils/adt/pgstatfuncs.c 17 3
src/include/catalog/pg_proc.dat 9 9
src/include/pgstat.h 6 0
src/test/regress/expected/rules.out 15 3
src/test/regress/expected/vacuum_stats.out 34 8
src/test/regress/sql/vacuum_stats.sql 24 2
From 66aafd7ac70686a164c35bf6bb3b8b757c16cf43 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <alenka.rybakina@gmail.com>
Date: Wed, 17 Jun 2026 21:46:14 +0300
Subject: [PATCH 7/9] Extended vacuum statistics: total shared-buffer access
 counters

Expose the shared-buffer access counters in pg_stat_vacuum_tables,
pg_stat_vacuum_indexes and the pg_stat_vacuum_database aggregate, with
documentation and regression coverage:

  total_blks_read     shared buffer blocks missed (read from disk)
  total_blks_hit      shared buffer blocks found in the buffer cache (hits)
  total_blks_dirtied  shared buffer blocks dirtied by this vacuum
  total_blks_written  shared buffer blocks written out

These counters are common to tables, indexes and the database aggregate
(where the first two are named db_blks_read/db_blks_hit); total_blks_dirtied
counts only the pages dirtied by this vacuum.
---
 doc/src/sgml/system-views.sgml             | 96 ++++++++++++++++++++++
 src/backend/access/heap/vacuumlazy.c       | 23 +++++-
 src/backend/catalog/system_views.sql       | 12 +++
 src/backend/utils/activity/pgstat_vacuum.c |  5 ++
 src/backend/utils/adt/pgstatfuncs.c        | 20 ++++-
 src/include/catalog/pg_proc.dat            | 18 ++--
 src/include/pgstat.h                       |  6 ++
 src/test/regress/expected/rules.out        | 18 +++-
 src/test/regress/expected/vacuum_stats.out | 42 ++++++++--
 src/test/regress/sql/vacuum_stats.sql      | 26 +++++-
 10 files changed, 240 insertions(+), 26 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1be54673bf..f37a3b64c1 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -5916,6 +5916,38 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        Number of heap pages newly marked both all-visible and all-frozen in the visibility map by the vacuum.
       </para></entry>
      </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks missed (read from disk) by the vacuum.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks found in the buffer cache (hits) by the vacuum.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_dirtied</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks dirtied by the vacuum.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_written</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks written out by the vacuum.
+      </para></entry>
+     </row>
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>wraparound_failsafe</structfield> <type>integer</type>
@@ -6039,6 +6071,38 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        Number of index entries removed by the vacuum.
       </para></entry>
      </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks read while vacuuming the index.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer block hits while vacuuming the index.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_dirtied</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks dirtied while vacuuming the index.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_written</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks written out while vacuuming the index.
+      </para></entry>
+     </row>
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>wal_records</structfield> <type>bigint</type>
@@ -6121,6 +6185,38 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        Number of vacuum operations in this database that failed with an error.
       </para></entry>
      </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>db_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks read by vacuum operations in this database.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>db_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer block hits by vacuum operations in this database.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_dirtied</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks dirtied by vacuum operations in this database.
+      </para></entry>
+     </row>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_written</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of shared buffer blocks written out by vacuum operations in this database.
+      </para></entry>
+     </row>
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>wraparound_failsafe</structfield> <type>integer</type>
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 46b7418136..1d6db2a91d 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -518,6 +518,7 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
 	memset(counters, 0, sizeof(LVExtStatCounters));
 
 	counters->walusage = pgWalUsage;
+	counters->bufusage = pgBufferUsage;
 }
 
 /* ----------
@@ -531,19 +532,27 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
 				 PgStat_CommonCounts * report)
 {
 	WalUsage	walusage;
+	BufferUsage bufusage;
 
 	if (!pgstat_track_vacuum_statistics)
 		return;
 
 	memset(report, 0, sizeof(PgStat_CommonCounts));
 
-	/* Calculate diffs of global stat parameters on WAL usage. */
+	/* Calculate diffs of global stat parameters on WAL and buffer usage. */
 	memset(&walusage, 0, sizeof(WalUsage));
 	WalUsageAccumDiff(&walusage, &pgWalUsage, &counters->walusage);
 
+	memset(&bufusage, 0, sizeof(BufferUsage));
+	BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &counters->bufusage);
+
 	/*
 	 * Fill additional statistics on a vacuum processing operation.
 	 */
+	report->total_blks_read += bufusage.local_blks_read + bufusage.shared_blks_read;
+	report->total_blks_hit += bufusage.local_blks_hit + bufusage.shared_blks_hit;
+	report->total_blks_dirtied += bufusage.local_blks_dirtied + bufusage.shared_blks_dirtied;
+	report->total_blks_written += bufusage.shared_blks_written;
 	report->wal_records += walusage.wal_records;
 	report->wal_fpi += walusage.wal_fpi;
 	report->wal_bytes += walusage.wal_bytes;
@@ -634,6 +643,10 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCount
 	extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
 	extVacStats->common.wraparound_failsafe_count = vacrel->wraparound_failsafe;
 
+	extVacStats->common.total_blks_dirtied -= vacrel->extVacReportIdx.common.total_blks_dirtied;
+	extVacStats->common.total_blks_hit -= vacrel->extVacReportIdx.common.total_blks_hit;
+	extVacStats->common.total_blks_read -= vacrel->extVacReportIdx.common.total_blks_read;
+	extVacStats->common.total_blks_written -= vacrel->extVacReportIdx.common.total_blks_written;
 	extVacStats->common.wal_bytes -= vacrel->extVacReportIdx.common.wal_bytes;
 	extVacStats->common.wal_fpi -= vacrel->extVacReportIdx.common.wal_fpi;
 	extVacStats->common.wal_records -= vacrel->extVacReportIdx.common.wal_records;
@@ -643,6 +656,10 @@ static void
 accumulate_idxs_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacIdxStats)
 {
 	/* Fill heap-specific extended stats fields */
+	vacrel->extVacReportIdx.common.total_blks_dirtied += extVacIdxStats->common.total_blks_dirtied;
+	vacrel->extVacReportIdx.common.total_blks_hit += extVacIdxStats->common.total_blks_hit;
+	vacrel->extVacReportIdx.common.total_blks_read += extVacIdxStats->common.total_blks_read;
+	vacrel->extVacReportIdx.common.total_blks_written += extVacIdxStats->common.total_blks_written;
 	vacrel->extVacReportIdx.common.wal_bytes += extVacIdxStats->common.wal_bytes;
 	vacrel->extVacReportIdx.common.wal_fpi += extVacIdxStats->common.wal_fpi;
 	vacrel->extVacReportIdx.common.wal_records += extVacIdxStats->common.wal_records;
@@ -664,6 +681,10 @@ extvac_accumulate_idx_report(PgStat_VacuumRelationCounts * dst,
 {
 	dst->type = PGSTAT_EXTVAC_INDEX;
 
+	dst->common.total_blks_read += src->common.total_blks_read;
+	dst->common.total_blks_hit += src->common.total_blks_hit;
+	dst->common.total_blks_dirtied += src->common.total_blks_dirtied;
+	dst->common.total_blks_written += src->common.total_blks_written;
 	dst->common.wal_records += src->common.wal_records;
 	dst->common.wal_fpi += src->common.wal_fpi;
 	dst->common.wal_bytes += src->common.wal_bytes;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index a7ac037bc3..1ee74f9273 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1576,6 +1576,10 @@ CREATE VIEW pg_stat_vacuum_tables AS
         S.vm_new_frozen_pages AS vm_new_frozen_pages,
         S.vm_new_visible_pages AS vm_new_visible_pages,
         S.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
+        S.total_blks_read AS total_blks_read,
+        S.total_blks_hit AS total_blks_hit,
+        S.total_blks_dirtied AS total_blks_dirtied,
+        S.total_blks_written AS total_blks_written,
         S.wraparound_failsafe AS wraparound_failsafe,
         S.wal_records AS wal_records,
         S.wal_fpi AS wal_fpi,
@@ -1597,6 +1601,10 @@ CREATE VIEW pg_stat_vacuum_indexes AS
             S.pages_deleted AS pages_deleted,
             S.tuples_deleted AS tuples_deleted,
 
+            S.total_blks_read AS total_blks_read,
+            S.total_blks_hit AS total_blks_hit,
+            S.total_blks_dirtied AS total_blks_dirtied,
+            S.total_blks_written AS total_blks_written,
             S.wal_records AS wal_records,
             S.wal_fpi AS wal_fpi,
             S.wal_bytes AS wal_bytes
@@ -1615,6 +1623,10 @@ CREATE VIEW pg_stat_vacuum_database AS
 
             S.errors AS errors,
 
+            S.db_blks_read AS db_blks_read,
+            S.db_blks_hit AS db_blks_hit,
+            S.total_blks_dirtied AS total_blks_dirtied,
+            S.total_blks_written AS total_blks_written,
             S.wraparound_failsafe AS wraparound_failsafe,
             S.wal_records AS wal_records,
             S.wal_fpi AS wal_fpi,
diff --git a/src/backend/utils/activity/pgstat_vacuum.c b/src/backend/utils/activity/pgstat_vacuum.c
index ec3d50c59c..2bb44da510 100644
--- a/src/backend/utils/activity/pgstat_vacuum.c
+++ b/src/backend/utils/activity/pgstat_vacuum.c
@@ -38,6 +38,11 @@ bool		pgstat_track_vacuum_statistics_for_relations = false;
 static void
 pgstat_accumulate_common(PgStat_CommonCounts *dst, const PgStat_CommonCounts *src)
 {
+	ACCUMULATE_FIELD(total_blks_read);
+	ACCUMULATE_FIELD(total_blks_hit);
+	ACCUMULATE_FIELD(total_blks_dirtied);
+	ACCUMULATE_FIELD(total_blks_written);
+
 	ACCUMULATE_FIELD(wal_records);
 	ACCUMULATE_FIELD(wal_fpi);
 	ACCUMULATE_FIELD(wal_bytes);
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 694fd220b0..eeca140354 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2374,7 +2374,7 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 15
+#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 19
 
 	Oid			relid = PG_GETARG_OID(0);
 	PgStat_VacuumRelationCounts *extvacuum;
@@ -2411,6 +2411,10 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
 	values[i++] = Int64GetDatum(extvacuum->table.vm_new_frozen_pages);
 	values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_pages);
 	values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_frozen_pages);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
 	values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
 	values[i++] = Int64GetDatum(extvacuum->common.wal_records);
 	values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
@@ -2432,7 +2436,7 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 6
+#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 10
 
 	Oid			relid = PG_GETARG_OID(0);
 	PgStat_VacuumRelationCounts *extvacuum;
@@ -2461,6 +2465,11 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
 	values[i++] = Int64GetDatum(extvacuum->index.pages_deleted);
 	values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
 
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
+
 	values[i++] = Int64GetDatum(extvacuum->common.wal_records);
 	values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
 	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
@@ -2480,7 +2489,7 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 7
+#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 11
 
 	Oid			dbid = PG_GETARG_OID(0);
 	PgStat_VacuumDBCounts *extvacuum;
@@ -2504,6 +2513,11 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
 
 	values[i++] = Int32GetDatum(extvacuum->errors);
 
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+	values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
+
 	values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
 
 	values[i++] = Int64GetDatum(extvacuum->common.wal_records);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d2fb1b4ec0..718ba80a3a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12643,9 +12643,9 @@
   proname => 'pg_stat_get_vacuum_tables', prorows => 1000, provolatile => 's', prorettype => 'record', proisstrict => 'f',
   proretset => 't',
   proargtypes => 'oid',
-  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int4,int8,int8,numeric}',
-  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{reloid,relid,pages_scanned,pages_removed,tuples_deleted,tuples_frozen,recently_dead_tuples,missed_dead_pages,missed_dead_tuples,vm_new_frozen_pages,vm_new_visible_pages,vm_new_visible_frozen_pages,wraparound_failsafe,wal_records,wal_fpi,wal_bytes}',
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int4,int8,int8,numeric}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{reloid,relid,pages_scanned,pages_removed,tuples_deleted,tuples_frozen,recently_dead_tuples,missed_dead_pages,missed_dead_tuples,vm_new_frozen_pages,vm_new_visible_pages,vm_new_visible_frozen_pages,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,wraparound_failsafe,wal_records,wal_fpi,wal_bytes}',
   prosrc => 'pg_stat_get_vacuum_tables' }
 
 # oid8 related functions
@@ -12718,17 +12718,17 @@
   proname => 'pg_stat_get_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record', proisstrict => 'f',
   proretset => 't',
   proargtypes => 'oid',
-  proallargtypes => '{oid,oid,int8,int8,int8,int8,numeric}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{reloid,relid,pages_deleted,tuples_deleted,wal_records,wal_fpi,wal_bytes}',
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,numeric}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{reloid,relid,pages_deleted,tuples_deleted,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,wal_records,wal_fpi,wal_bytes}',
   prosrc => 'pg_stat_get_vacuum_indexes' },
 { oid => '8005',
   descr => 'pg_stat_get_vacuum_database returns vacuum stats values for database',
   proname => 'pg_stat_get_vacuum_database', prorows => 1000, provolatile => 's', prorettype => 'record', proisstrict => 'f',
   proretset => 't',
   proargtypes => 'oid',
-  proallargtypes => '{oid,oid,int4,int4,int8,int8,numeric,int4}',
-  proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dbid,dboid,errors,wraparound_failsafe,wal_records,wal_fpi,wal_bytes,interrupts_count}',
+  proallargtypes => '{oid,oid,int4,int8,int8,int8,int8,int4,int8,int8,numeric,int4}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{dbid,dboid,errors,db_blks_read,db_blks_hit,total_blks_dirtied,total_blks_written,wraparound_failsafe,wal_records,wal_fpi,wal_bytes,interrupts_count}',
   prosrc => 'pg_stat_get_vacuum_database' },
 ]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index bcc84dbd41..92f8b9b8ec 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -175,6 +175,12 @@ typedef struct PgStat_TableCounts
 
 typedef struct PgStat_CommonCounts
 {
+	/* blocks */
+	int64		total_blks_read;
+	int64		total_blks_hit;
+	int64		total_blks_dirtied;
+	int64		total_blks_written;
+
 	/* WAL */
 	int64		wal_records;
 	int64		wal_fpi;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 27a17ee0c2..2b879192aa 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2424,13 +2424,17 @@ pg_stat_user_tables| SELECT relid,
 pg_stat_vacuum_database| SELECT d.oid AS dboid,
     d.datname AS dbname,
     s.errors,
+    s.db_blks_read,
+    s.db_blks_hit,
+    s.total_blks_dirtied,
+    s.total_blks_written,
     s.wraparound_failsafe,
     s.wal_records,
     s.wal_fpi,
     s.wal_bytes,
     s.interrupts_count
    FROM pg_database d,
-    LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, errors, wraparound_failsafe, wal_records, wal_fpi, wal_bytes, interrupts_count);
+    LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, errors, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wraparound_failsafe, wal_records, wal_fpi, wal_bytes, interrupts_count);
 pg_stat_vacuum_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2438,6 +2442,10 @@ pg_stat_vacuum_indexes| SELECT c.oid AS relid,
     i.relname AS indexrelname,
     s.pages_deleted,
     s.tuples_deleted,
+    s.total_blks_read,
+    s.total_blks_hit,
+    s.total_blks_dirtied,
+    s.total_blks_written,
     s.wal_records,
     s.wal_fpi,
     s.wal_bytes
@@ -2445,7 +2453,7 @@ pg_stat_vacuum_indexes| SELECT c.oid AS relid,
      JOIN pg_index x ON ((c.oid = x.indrelid)))
      JOIN pg_class i ON ((i.oid = x.indexrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
-    LATERAL pg_stat_get_vacuum_indexes(i.oid) s(relid, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes)
+    LATERAL pg_stat_get_vacuum_indexes(i.oid) s(relid, pages_deleted, tuples_deleted, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes)
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
 pg_stat_vacuum_tables| SELECT n.nspname AS schemaname,
     c.relname,
@@ -2460,13 +2468,17 @@ pg_stat_vacuum_tables| SELECT n.nspname AS schemaname,
     s.vm_new_frozen_pages,
     s.vm_new_visible_pages,
     s.vm_new_visible_frozen_pages,
+    s.total_blks_read,
+    s.total_blks_hit,
+    s.total_blks_dirtied,
+    s.total_blks_written,
     s.wraparound_failsafe,
     s.wal_records,
     s.wal_fpi,
     s.wal_bytes
    FROM (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
-    LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, pages_scanned, pages_removed, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_pages, missed_dead_tuples, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, wraparound_failsafe, wal_records, wal_fpi, wal_bytes)
+    LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, pages_scanned, pages_removed, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_pages, missed_dead_tuples, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, wraparound_failsafe, wal_records, wal_fpi, wal_bytes)
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
diff --git a/src/test/regress/expected/vacuum_stats.out b/src/test/regress/expected/vacuum_stats.out
index c996699804..87f49cb76c 100644
--- a/src/test/regress/expected/vacuum_stats.out
+++ b/src/test/regress/expected/vacuum_stats.out
@@ -123,6 +123,21 @@ SELECT vm_new_visible_pages > 0 AS vm_new_visible_pages,
 (1 row)
 
 DROP TABLE vacstat_frz;
+-- total buffer access counters.  The vacuum always touches the table's pages
+-- through the buffer cache (total_blks_hit > 0) and dirties some of them while
+-- removing dead tuples (total_blks_dirtied > 0).  total_blks_read and
+-- total_blks_written depend on the buffer-cache and checkpoint state at run
+-- time, so they are only checked for being non-negative.
+SELECT total_blks_read >= 0 AS total_blks_read,
+       total_blks_hit > 0 AS total_blks_hit,
+       total_blks_dirtied > 0 AS total_blks_dirtied,
+       total_blks_written >= 0 AS total_blks_written
+  FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t';
+ total_blks_read | total_blks_hit | total_blks_dirtied | total_blks_written 
+-----------------+----------------+--------------------+--------------------
+ t               | t              | t                  | t
+(1 row)
+
 -- WAL metrics.  A vacuum that removes tuples always emits WAL
 -- (wal_records > 0, wal_bytes > 0).  wal_fpi depends on whether a checkpoint
 -- happened recently, so it is only checked for being non-negative here; the
@@ -174,17 +189,23 @@ SELECT wraparound_failsafe = 0 AS wraparound_failsafe
 
 -- per-index view: the primary key index is processed by the same VACUUM.
 -- No btree leaf empties out (interleaved deletions), so pages_deleted = 0,
--- while every index entry for a removed heap tuple is deleted.
+-- while every index entry for a removed heap tuple is deleted.  The index is
+-- read through the buffer cache (total_blks_hit > 0); the read/written/dirtied
+-- counters depend on run-time cache state.
 SELECT indexrelname,
        pages_deleted = 0 AS pages_deleted,
        tuples_deleted = 500 AS tuples_deleted,
+       total_blks_read >= 0 AS total_blks_read,
+       total_blks_hit > 0 AS total_blks_hit,
+       total_blks_dirtied >= 0 AS total_blks_dirtied,
+       total_blks_written >= 0 AS total_blks_written,
        wal_records > 0 AS wal_records,
        wal_fpi >= 0 AS wal_fpi,
        wal_bytes > 0 AS wal_bytes
   FROM pg_stat_vacuum_indexes WHERE relname = 'vacstat_t' ORDER BY indexrelname;
-  indexrelname  | pages_deleted | tuples_deleted | wal_records | wal_fpi | wal_bytes 
-----------------+---------------+----------------+-------------+---------+-----------
- vacstat_t_pkey | t             | t              | t           | t       | t
+  indexrelname  | pages_deleted | tuples_deleted | total_blks_read | total_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes 
+----------------+---------------+----------------+-----------------+----------------+--------------------+--------------------+-------------+---------+-----------
+ vacstat_t_pkey | t             | t              | t               | t              | t                  | t                  | t           | t       | t
 (1 row)
 
 -- index page-deletion path: deleting a contiguous key range empties whole
@@ -219,16 +240,21 @@ SELECT indexrelname,
 
 DROP TABLE vacstat_idxdel;
 -- per-database aggregate view: no vacuum errors occurred in this database, and
--- the vacuums in this database emit WAL (wal_records > 0).
+-- the vacuums in this database touched pages through the buffer cache
+-- (db_blks_hit > 0) and emit WAL (wal_records > 0).
 SELECT errors = 0 AS errors,
+       db_blks_read >= 0 AS db_blks_read,
+       db_blks_hit > 0 AS db_blks_hit,
+       total_blks_dirtied >= 0 AS total_blks_dirtied,
+       total_blks_written >= 0 AS total_blks_written,
        wraparound_failsafe = 0 AS wraparound_failsafe,
        wal_records > 0 AS wal_records,
        wal_fpi >= 0 AS wal_fpi,
        wal_bytes > 0 AS wal_bytes
   FROM pg_stat_vacuum_database WHERE dbname = current_database();
- errors | wraparound_failsafe | wal_records | wal_fpi | wal_bytes 
---------+---------------------+-------------+---------+-----------
- t      | t                   | t           | t       | t
+ errors | db_blks_read | db_blks_hit | total_blks_dirtied | total_blks_written | wraparound_failsafe | wal_records | wal_fpi | wal_bytes 
+--------+--------------+-------------+--------------------+--------------------+---------------------+-------------+---------+-----------
+ t      | t            | t           | t                  | t                  | t                   | t           | t       | t
 (1 row)
 
 -- parallel index vacuum: index statistics must be captured for indexes
diff --git a/src/test/regress/sql/vacuum_stats.sql b/src/test/regress/sql/vacuum_stats.sql
index f106b7c37b..6e364d183c 100644
--- a/src/test/regress/sql/vacuum_stats.sql
+++ b/src/test/regress/sql/vacuum_stats.sql
@@ -81,6 +81,17 @@ SELECT vm_new_visible_pages > 0 AS vm_new_visible_pages,
   FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_frz';
 DROP TABLE vacstat_frz;
 
+-- total buffer access counters.  The vacuum always touches the table's pages
+-- through the buffer cache (total_blks_hit > 0) and dirties some of them while
+-- removing dead tuples (total_blks_dirtied > 0).  total_blks_read and
+-- total_blks_written depend on the buffer-cache and checkpoint state at run
+-- time, so they are only checked for being non-negative.
+SELECT total_blks_read >= 0 AS total_blks_read,
+       total_blks_hit > 0 AS total_blks_hit,
+       total_blks_dirtied > 0 AS total_blks_dirtied,
+       total_blks_written >= 0 AS total_blks_written
+  FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t';
+
 -- WAL metrics.  A vacuum that removes tuples always emits WAL
 -- (wal_records > 0, wal_bytes > 0).  wal_fpi depends on whether a checkpoint
 -- happened recently, so it is only checked for being non-negative here; the
@@ -115,10 +126,16 @@ SELECT wraparound_failsafe = 0 AS wraparound_failsafe
 
 -- per-index view: the primary key index is processed by the same VACUUM.
 -- No btree leaf empties out (interleaved deletions), so pages_deleted = 0,
--- while every index entry for a removed heap tuple is deleted.
+-- while every index entry for a removed heap tuple is deleted.  The index is
+-- read through the buffer cache (total_blks_hit > 0); the read/written/dirtied
+-- counters depend on run-time cache state.
 SELECT indexrelname,
        pages_deleted = 0 AS pages_deleted,
        tuples_deleted = 500 AS tuples_deleted,
+       total_blks_read >= 0 AS total_blks_read,
+       total_blks_hit > 0 AS total_blks_hit,
+       total_blks_dirtied >= 0 AS total_blks_dirtied,
+       total_blks_written >= 0 AS total_blks_written,
        wal_records > 0 AS wal_records,
        wal_fpi >= 0 AS wal_fpi,
        wal_bytes > 0 AS wal_bytes
@@ -142,8 +159,13 @@ SELECT indexrelname,
 DROP TABLE vacstat_idxdel;
 
 -- per-database aggregate view: no vacuum errors occurred in this database, and
--- the vacuums in this database emit WAL (wal_records > 0).
+-- the vacuums in this database touched pages through the buffer cache
+-- (db_blks_hit > 0) and emit WAL (wal_records > 0).
 SELECT errors = 0 AS errors,
+       db_blks_read >= 0 AS db_blks_read,
+       db_blks_hit > 0 AS db_blks_hit,
+       total_blks_dirtied >= 0 AS total_blks_dirtied,
+       total_blks_written >= 0 AS total_blks_written,
        wraparound_failsafe = 0 AS wraparound_failsafe,
        wal_records > 0 AS wal_records,
        wal_fpi >= 0 AS wal_fpi,
-- 
2.39.5 (Apple Git-154)