v26-0004-Vacuum-statistics-have-been-separated-from-regular.patch

text/plain

Filename: v26-0004-Vacuum-statistics-have-been-separated-from-regular.patch
Type: text/plain
Part: 3
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 v26-0004
Subject: Vacuum statistics have been separated from regular relation and database statistics to reduce memory usage. Dedicated PGSTAT_KIND_VACUUM_RELATION and PGSTAT_KIND_VACUUM_DB entries were added to the stats collector to efficiently allocate memory for vacuum-specific metrics, which require significantly more space per relation.
File+
src/backend/access/heap/vacuumlazy.c 72 52
src/backend/catalog/heap.c 1 0
src/backend/catalog/index.c 1 0
src/backend/catalog/system_views.sql 91 86
src/backend/commands/dbcommands.c 1 0
src/backend/commands/vacuumparallel.c 2 3
src/backend/utils/activity/Makefile 1 0
src/backend/utils/activity/pgstat.c 29 1
src/backend/utils/activity/pgstat_database.c 9 1
src/backend/utils/activity/pgstat_relation.c 7 68
src/backend/utils/activity/pgstat_vacuum.c 214 0
src/backend/utils/adt/pgstatfuncs.c 77 72
src/backend/utils/misc/guc_parameters.dat 6 0
src/include/commands/vacuum.h 1 1
src/include/pgstat.h 114 94
src/include/utils/pgstat_internal.h 15 0
src/include/utils/pgstat_kind.h 3 1
src/test/recovery/t/050_vacuum_extending_basic_test.pl 15 6
src/test/recovery/t/051_vacuum_extending_freeze_test.pl 1 0
src/test/regress/expected/rules.out 75 71
From 12f4596c25e15d1f7566b87334615ad112cfb64e Mon Sep 17 00:00:00 2001
From: Alena Rybakina <a.rybakina@postgrespro.ru>
Date: Sun, 21 Dec 2025 01:40:06 +0300
Subject: [PATCH 4/5] Vacuum statistics have been separated from regular
 relation and database statistics to reduce memory usage. Dedicated
 PGSTAT_KIND_VACUUM_RELATION and PGSTAT_KIND_VACUUM_DB entries were added to
 the stats collector to efficiently allocate memory for vacuum-specific
 metrics, which require significantly more space per relation.

---
 src/backend/access/heap/vacuumlazy.c          | 124 +++++-----
 src/backend/catalog/heap.c                    |   1 +
 src/backend/catalog/index.c                   |   1 +
 src/backend/catalog/system_views.sql          | 177 ++++++++-------
 src/backend/commands/dbcommands.c             |   1 +
 src/backend/commands/vacuumparallel.c         |   5 +-
 src/backend/utils/activity/Makefile           |   1 +
 src/backend/utils/activity/pgstat.c           |  30 ++-
 src/backend/utils/activity/pgstat_database.c  |  10 +-
 src/backend/utils/activity/pgstat_relation.c  |  75 +-----
 src/backend/utils/activity/pgstat_vacuum.c    | 214 ++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c           | 149 ++++++------
 src/backend/utils/misc/guc_parameters.dat     |   6 +
 src/include/commands/vacuum.h                 |   2 +-
 src/include/pgstat.h                          | 208 +++++++++--------
 src/include/utils/pgstat_internal.h           |  15 ++
 src/include/utils/pgstat_kind.h               |   4 +-
 .../t/050_vacuum_extending_basic_test.pl      |  21 +-
 .../t/051_vacuum_extending_freeze_test.pl     |   1 +
 src/test/regress/expected/rules.out           | 146 ++++++------
 20 files changed, 735 insertions(+), 456 deletions(-)
 create mode 100644 src/backend/utils/activity/pgstat_vacuum.c

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index fcd92a43dda..3f1ed040908 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -413,7 +413,8 @@ typedef struct LVRelState
 	int32		wraparound_failsafe_count;	/* number of emergency vacuums to
 											 * prevent anti-wraparound
 											 * shutdown */
-	ExtVacReport extVacReportIdx;
+
+	PgStat_VacuumRelationCounts extVacReportIdx;
 } LVRelState;
 
 
@@ -505,6 +506,9 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
 {
 	TimestampTz starttime;
 
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
 	memset(counters, 0, sizeof(LVExtStatCounters));
 
 	starttime = GetCurrentTimestamp();
@@ -536,7 +540,7 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
  */
 static void
 extvac_stats_end(Relation rel, LVExtStatCounters * counters,
-				 ExtVacReport * report)
+				 PgStat_CommonCounts * report)
 {
 	WalUsage	walusage;
 	BufferUsage bufusage;
@@ -544,6 +548,11 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
 	long		secs;
 	int			usecs;
 
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
+	memset(report, 0, sizeof(PgStat_CommonCounts));
+
 	/* Calculate diffs of global stat parameters on WAL and buffer usage. */
 	memset(&walusage, 0, sizeof(WalUsage));
 	WalUsageAccumDiff(&walusage, &pgWalUsage, &counters->walusage);
@@ -592,6 +601,9 @@ void
 extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
 					   LVExtStatCountersIdx * counters)
 {
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
 	/* Set initial values for common heap and index statistics */
 	extvac_stats_start(rel, &counters->common);
 	counters->pages_deleted = counters->tuples_removed = 0;
@@ -609,11 +621,15 @@ extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
 
 void
 extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
-					 LVExtStatCountersIdx * counters, ExtVacReport * report)
+					 LVExtStatCountersIdx * counters, PgStat_VacuumRelationCounts * report)
 {
-	memset(report, 0, sizeof(ExtVacReport));
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
+	memset(report, 0, sizeof(PgStat_VacuumRelationCounts));
+
+	extvac_stats_end(rel, &counters->common, &report->common);
 
-	extvac_stats_end(rel, &counters->common, report);
 	report->type = PGSTAT_EXTVAC_INDEX;
 
 	if (stats != NULL)
@@ -624,7 +640,7 @@ extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
 		 */
 
 		/* Fill index-specific extended stats fields */
-		report->tuples_deleted =
+		report->common.tuples_deleted =
 			stats->tuples_removed - counters->tuples_removed;
 		report->index.pages_deleted =
 			stats->pages_deleted - counters->pages_deleted;
@@ -647,8 +663,11 @@ extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
   * procudure.
 */
 static void
-accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats)
+accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacStats)
 {
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
 	/* Fill heap-specific extended stats fields */
 	extVacStats->type = PGSTAT_EXTVAC_TABLE;
 	extVacStats->table.pages_scanned = vacrel->scanned_pages;
@@ -656,46 +675,45 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats
 	extVacStats->table.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
 	extVacStats->table.vm_new_visible_pages = vacrel->vm_new_visible_pages;
 	extVacStats->table.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
-	extVacStats->tuples_deleted = vacrel->tuples_deleted;
+	extVacStats->common.tuples_deleted = vacrel->tuples_deleted;
 	extVacStats->table.tuples_frozen = vacrel->tuples_frozen;
 	extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
 	extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
 	extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
 	extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
 	extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
-	extVacStats->wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+	extVacStats->common.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
 
-	extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
-	extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
-	extVacStats->total_blks_dirtied -= vacrel->extVacReportIdx.total_blks_dirtied;
-	extVacStats->total_blks_hit -= vacrel->extVacReportIdx.total_blks_hit;
-	extVacStats->total_blks_read -= vacrel->extVacReportIdx.total_blks_read;
-	extVacStats->total_blks_written -= vacrel->extVacReportIdx.total_blks_written;
-	extVacStats->wal_bytes -= vacrel->extVacReportIdx.wal_bytes;
-	extVacStats->wal_fpi -= vacrel->extVacReportIdx.wal_fpi;
-	extVacStats->wal_records -= vacrel->extVacReportIdx.wal_records;
+	extVacStats->common.blk_read_time -= vacrel->extVacReportIdx.common.blk_read_time;
+	extVacStats->common.blk_write_time -= vacrel->extVacReportIdx.common.blk_write_time;
+	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;
 
-	extVacStats->total_time -= vacrel->extVacReportIdx.total_time;
-	extVacStats->delay_time -= vacrel->extVacReportIdx.delay_time;
+	extVacStats->common.total_time -= vacrel->extVacReportIdx.common.total_time;
+	extVacStats->common.delay_time -= vacrel->extVacReportIdx.common.delay_time;
 
 }
 
 static void
-accumulate_idxs_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacIdxStats)
+accumulate_idxs_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacIdxStats)
 {
 	/* Fill heap-specific extended stats fields */
-	vacrel->extVacReportIdx.blk_read_time += extVacIdxStats->blk_read_time;
-	vacrel->extVacReportIdx.blk_write_time += extVacIdxStats->blk_write_time;
-	vacrel->extVacReportIdx.total_blks_dirtied += extVacIdxStats->total_blks_dirtied;
-	vacrel->extVacReportIdx.total_blks_hit += extVacIdxStats->total_blks_hit;
-	vacrel->extVacReportIdx.total_blks_read += extVacIdxStats->total_blks_read;
-	vacrel->extVacReportIdx.total_blks_written += extVacIdxStats->total_blks_written;
-	vacrel->extVacReportIdx.wal_bytes += extVacIdxStats->wal_bytes;
-	vacrel->extVacReportIdx.wal_fpi += extVacIdxStats->wal_fpi;
-	vacrel->extVacReportIdx.wal_records += extVacIdxStats->wal_records;
-	vacrel->extVacReportIdx.delay_time += extVacIdxStats->delay_time;
-
-	vacrel->extVacReportIdx.total_time += extVacIdxStats->total_time;
+	vacrel->extVacReportIdx.common.blk_read_time += extVacIdxStats->common.blk_read_time;
+	vacrel->extVacReportIdx.common.blk_write_time += extVacIdxStats->common.blk_write_time;
+	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;
+	vacrel->extVacReportIdx.common.delay_time += extVacIdxStats->common.delay_time;
+	vacrel->extVacReportIdx.common.total_time += extVacIdxStats->common.total_time;
 }
 
 
@@ -856,10 +874,10 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 	ErrorContextCallback errcallback;
 	char	  **indnames = NULL;
 	LVExtStatCounters extVacCounters;
-	ExtVacReport extVacReport;
+	PgStat_VacuumRelationCounts extVacReport;
 
 	/* Initialize vacuum statistics */
-	memset(&extVacReport, 0, sizeof(ExtVacReport));
+	memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts));
 
 	verbose = (params.options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -915,7 +933,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 	errcallback.previous = error_context_stack;
 	error_context_stack = &errcallback;
 
-	memset(&vacrel->extVacReportIdx, 0, sizeof(ExtVacReport));
+	memset(&vacrel->extVacReportIdx, 0, sizeof(PgStat_VacuumRelationCounts));
+	memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
 
 	/* Set up high level stuff about rel and its indexes */
 	vacrel->rel = rel;
@@ -1173,7 +1192,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 						&frozenxid_updated, &minmulti_updated, false);
 
 	/* Make generic extended vacuum stats report */
-	extvac_stats_end(rel, &extVacCounters, &extVacReport);
+	/* extvac_stats_end(rel, &extVacCounters, &extVacReport.common); */
 
 	/*
 	 * Report results to the cumulative stats system, too.
@@ -1190,14 +1209,14 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
 	 * Make generic extended vacuum stats report and fill heap-specific
 	 * extended stats fields.
 	 */
-	extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport);
+	extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport.common);
 	accumulate_heap_vacuum_statistics(vacrel, &extVacReport);
+	pgstat_report_vacuum_extstats(vacrel->reloid, rel->rd_rel->relisshared, &extVacReport);
 	pgstat_report_vacuum(rel,
 						 Max(vacrel->new_live_tuples, 0),
 						 vacrel->recently_dead_tuples +
 						 vacrel->missed_dead_tuples,
-						 starttime,
-						 &extVacReport);
+						 starttime);
 	pgstat_progress_end_command();
 
 	if (instrument)
@@ -2902,9 +2921,9 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
 	else
 	{
 		LVExtStatCounters counters;
-		ExtVacReport extVacReport;
+		PgStat_VacuumRelationCounts extVacReport;
 
-		memset(&extVacReport, 0, sizeof(ExtVacReport));
+		memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
 
 		extvac_stats_start(vacrel->rel, &counters);
 
@@ -2912,7 +2931,7 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
 		parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples,
 											vacrel->num_index_scans);
 
-		extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+		extvac_stats_end(vacrel->rel, &counters, &extVacReport.common);
 		accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
 
 		/*
@@ -3345,9 +3364,9 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
 	else
 	{
 		LVExtStatCounters counters;
-		ExtVacReport extVacReport;
+		PgStat_VacuumRelationCounts extVacReport;
 
-		memset(&extVacReport, 0, sizeof(ExtVacReport));
+		memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
 
 		extvac_stats_start(vacrel->rel, &counters);
 
@@ -3356,7 +3375,7 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
 											vacrel->num_index_scans,
 											estimated_count);
 
-		extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+		extvac_stats_end(vacrel->rel, &counters, &extVacReport.common);
 		accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
 	}
 
@@ -3384,7 +3403,10 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	IndexVacuumInfo ivinfo;
 	LVSavedErrInfo saved_err_info;
 	LVExtStatCountersIdx extVacCounters;
-	ExtVacReport extVacReport;
+	PgStat_VacuumRelationCounts extVacReport;
+
+	memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts));
+	memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
 
 	/* Set initial statistics values to gather vacuum statistics for the index */
 	extvac_stats_start_idx(indrel, istat, &extVacCounters);
@@ -3421,8 +3443,7 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	if (!ParallelVacuumIsActive(vacrel))
 		accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
 
-	pgstat_report_vacuum(indrel,
-						 0, 0, 0, &extVacReport);
+	pgstat_report_vacuum_extstats(vacrel->indoid, indrel->rd_rel->relisshared, &extVacReport);
 
 	/* Revert to the previous phase information for error traceback */
 	restore_vacuum_error_info(vacrel, &saved_err_info);
@@ -3449,7 +3470,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	IndexVacuumInfo ivinfo;
 	LVSavedErrInfo saved_err_info;
 	LVExtStatCountersIdx extVacCounters;
-	ExtVacReport extVacReport;
+	PgStat_VacuumRelationCounts extVacReport;
 
 	/* Set initial statistics values to gather vacuum statistics for the index */
 	extvac_stats_start_idx(indrel, istat, &extVacCounters);
@@ -3485,8 +3506,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	if (!ParallelVacuumIsActive(vacrel))
 		accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
 
-	pgstat_report_vacuum(indrel,
-						 0, 0, 0, &extVacReport);
+	pgstat_report_vacuum_extstats(RelationGetRelid(indrel), indrel->rd_rel->relisshared, &extVacReport);
 
 	/* Revert to the previous phase information for error traceback */
 	restore_vacuum_error_info(vacrel, &saved_err_info);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 265cc3e5fbf..680b76b8ef9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1883,6 +1883,7 @@ heap_drop_with_catalog(Oid relid)
 
 	/* ensure that stats are dropped if transaction commits */
 	pgstat_drop_relation(rel);
+	pgstat_vacuum_relation_delete_pending_cb(RelationGetRelid(rel));
 
 	/*
 	 * Close relcache entry, but *keep* AccessExclusiveLock on the relation
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8dea58ad96b..e906f9e1856 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2327,6 +2327,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 
 	/* ensure that stats are dropped if transaction commits */
 	pgstat_drop_relation(userIndexRelation);
+	pgstat_vacuum_relation_delete_pending_cb(RelationGetRelid(userIndexRelation));
 
 	/*
 	 * Close and flush the index's relcache entry, to ensure relcache doesn't
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index dc86b1ee212..4ec2a7d9f10 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1462,99 +1462,104 @@ GRANT EXECUTE ON FUNCTION pg_get_aios() TO pg_read_all_stats;
 --
 
 CREATE VIEW pg_stat_vacuum_tables AS
-SELECT
-  ns.nspname AS schemaname,
-  rel.relname AS relname,
-  stats.relid as relid,
-
-  stats.total_blks_read AS total_blks_read,
-  stats.total_blks_hit AS total_blks_hit,
-  stats.total_blks_dirtied AS total_blks_dirtied,
-  stats.total_blks_written AS total_blks_written,
-
-  stats.rel_blks_read AS rel_blks_read,
-  stats.rel_blks_hit AS rel_blks_hit,
-
-  stats.pages_scanned AS pages_scanned,
-  stats.pages_removed AS pages_removed,
-  stats.vm_new_frozen_pages AS vm_new_frozen_pages,
-  stats.vm_new_visible_pages AS vm_new_visible_pages,
-  stats.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
-  stats.missed_dead_pages AS missed_dead_pages,
-  stats.tuples_deleted AS tuples_deleted,
-  stats.tuples_frozen AS tuples_frozen,
-  stats.recently_dead_tuples AS recently_dead_tuples,
-  stats.missed_dead_tuples AS missed_dead_tuples,
-
-  stats.wraparound_failsafe AS wraparound_failsafe,
-  stats.index_vacuum_count AS index_vacuum_count,
-  stats.wal_records AS wal_records,
-  stats.wal_fpi AS wal_fpi,
-  stats.wal_bytes AS wal_bytes,
-
-  stats.blk_read_time AS blk_read_time,
-  stats.blk_write_time AS blk_write_time,
-
-  stats.delay_time AS delay_time,
-  stats.total_time AS total_time
-
-FROM pg_class rel
-  JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
-  LATERAL pg_stat_get_vacuum_tables(rel.oid) stats
-WHERE rel.relkind = 'r';
+    SELECT
+        N.nspname AS schemaname,
+        C.relname AS relname,
+        S.relid as relid,
+
+        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.rel_blks_read AS rel_blks_read,
+        S.rel_blks_hit AS rel_blks_hit,
+
+        S.pages_scanned AS pages_scanned,
+        S.pages_removed AS pages_removed,
+        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.missed_dead_pages AS missed_dead_pages,
+        S.tuples_deleted AS tuples_deleted,
+        S.tuples_frozen AS tuples_frozen,
+        S.recently_dead_tuples AS recently_dead_tuples,
+        S.missed_dead_tuples AS missed_dead_tuples,
+
+        S.wraparound_failsafe AS wraparound_failsafe,
+        S.index_vacuum_count AS index_vacuum_count,
+        S.wal_records AS wal_records,
+        S.wal_fpi AS wal_fpi,
+        S.wal_bytes AS wal_bytes,
+
+        S.blk_read_time AS blk_read_time,
+        S.blk_write_time AS blk_write_time,
+
+        S.delay_time AS delay_time,
+        S.total_time AS total_time
+
+    FROM pg_class C JOIN
+            pg_namespace N ON N.oid = C.relnamespace,
+            LATERAL pg_stat_get_vacuum_tables(C.oid) S
+    WHERE C.relkind IN ('r', 't', 'm');
 
 CREATE VIEW pg_stat_vacuum_indexes AS
-SELECT
-  rel.oid as relid,
-  ns.nspname AS schemaname,
-  rel.relname AS relname,
+    SELECT
+            C.oid AS relid,
+            I.oid AS indexrelid,
+            N.nspname AS schemaname,
+            C.relname AS relname,
+            I.relname AS indexrelname,
 
-  total_blks_read AS total_blks_read,
-  total_blks_hit AS total_blks_hit,
-  total_blks_dirtied AS total_blks_dirtied,
-  total_blks_written AS total_blks_written,
+            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,
 
-  rel_blks_read AS rel_blks_read,
-  rel_blks_hit AS rel_blks_hit,
+            S.rel_blks_read AS rel_blks_read,
+            S.rel_blks_hit AS rel_blks_hit,
 
-  pages_deleted AS pages_deleted,
-  tuples_deleted AS tuples_deleted,
+            S.pages_deleted AS pages_deleted,
+            S.tuples_deleted AS tuples_deleted,
 
-  wal_records AS wal_records,
-  wal_fpi AS wal_fpi,
-  wal_bytes AS wal_bytes,
+            S.wal_records AS wal_records,
+            S.wal_fpi AS wal_fpi,
+            S.wal_bytes AS wal_bytes,
 
-  blk_read_time AS blk_read_time,
-  blk_write_time AS blk_write_time,
+            S.blk_read_time AS blk_read_time,
+            S.blk_write_time AS blk_write_time,
 
-  delay_time AS delay_time,
-  total_time AS total_time
-FROM
-  pg_class rel
-  JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
-  LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
-WHERE rel.relkind = 'i';
+            S.delay_time AS delay_time,
+            S.total_time AS total_time
+    FROM
+            pg_class C 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
+    WHERE C.relkind IN ('r', 't', 'm');
 
 CREATE VIEW pg_stat_vacuum_database AS
-SELECT
-  db.oid as dboid,
-  db.datname AS dbname,
-
-  stats.db_blks_read AS db_blks_read,
-  stats.db_blks_hit AS db_blks_hit,
-  stats.total_blks_dirtied AS total_blks_dirtied,
-  stats.total_blks_written AS total_blks_written,
-
-  stats.wal_records AS wal_records,
-  stats.wal_fpi AS wal_fpi,
-  stats.wal_bytes AS wal_bytes,
-
-  stats.blk_read_time AS blk_read_time,
-  stats.blk_write_time AS blk_write_time,
-
-  stats.delay_time AS delay_time,
-  stats.total_time AS total_time,
-  stats.wraparound_failsafe AS wraparound_failsafe
-FROM
-  pg_database db,
-  LATERAL pg_stat_get_vacuum_database(db.oid) stats;
\ No newline at end of file
+    SELECT
+            D.oid as dboid,
+            D.datname AS dbname,
+
+            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.wal_records AS wal_records,
+            S.wal_fpi AS wal_fpi,
+            S.wal_bytes AS wal_bytes,
+
+            S.blk_read_time AS blk_read_time,
+            S.blk_write_time AS blk_write_time,
+
+            S.delay_time AS delay_time,
+            S.total_time AS total_time,
+            S.wraparound_failsafe AS wraparound_failsafe,
+            S.errors AS errors
+    FROM
+            pg_database D,
+            LATERAL pg_stat_get_vacuum_database(D.oid) S;
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index d1f3be89b35..bf3cd3b1cc9 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -1815,6 +1815,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 	 * Tell the cumulative stats system to forget it immediately, too.
 	 */
 	pgstat_drop_database(db_id);
+	pgstat_drop_vacuum_database(db_id);
 
 	/*
 	 * Except for the deletion of the catalog row, subsequent actions are not
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 43450685b09..c7dd2bb52f6 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -869,7 +869,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
 	IndexBulkDeleteResult *istat_res;
 	IndexVacuumInfo ivinfo;
 	LVExtStatCountersIdx extVacCounters;
-	ExtVacReport extVacReport;
+	PgStat_VacuumRelationCounts extVacReport;
 
 	/*
 	 * Update the pointer to the corresponding bulk-deletion result if someone
@@ -911,8 +911,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
 
 	/* Make extended vacuum stats report for index */
 	extvac_stats_end_idx(indrel, istat_res, &extVacCounters, &extVacReport);
-	pgstat_report_vacuum(indrel,
-						 0, 0, 0, &extVacReport);
+	pgstat_report_vacuum_extstats(RelationGetRelid(indrel), indrel->rd_rel->relisshared, &extVacReport);
 
 	/*
 	 * Copy the index bulk-deletion result returned from ambulkdelete and
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 9c2443e1ecd..183f7514d2d 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -27,6 +27,7 @@ OBJS = \
 	pgstat_function.o \
 	pgstat_io.o \
 	pgstat_relation.o \
+	pgstat_vacuum.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index f317c6e8e90..cdc9cab01cf 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -203,7 +203,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind);
 
 bool		pgstat_track_counts = false;
 int			pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE;
-
+bool		pgstat_track_vacuum_statistics = false;
 
 /* ----------
  * state shared with pgstat_*.c
@@ -482,6 +482,34 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
 		.reset_all_cb = pgstat_wal_reset_all_cb,
 		.snapshot_cb = pgstat_wal_snapshot_cb,
 	},
+	[PGSTAT_KIND_VACUUM_DB] = {
+		.name = "vacuum statistics",
+
+		.fixed_amount = false,
+		.write_to_file = true,
+		/* so pg_stat_database entries can be seen in all databases */
+		.accessed_across_databases = true,
+
+		.shared_size = sizeof(PgStatShared_VacuumDB),
+		.shared_data_off = offsetof(PgStatShared_VacuumDB, stats),
+		.shared_data_len = sizeof(((PgStatShared_VacuumDB *) 0)->stats),
+		.pending_size = sizeof(PgStat_VacuumDBCounts),
+
+		.flush_pending_cb = pgstat_vacuum_db_flush_cb,
+	},
+	[PGSTAT_KIND_VACUUM_RELATION] = {
+		.name = "vacuum statistics",
+
+		.fixed_amount = false,
+		.write_to_file = true,
+
+		.shared_size = sizeof(PgStatShared_VacuumRelation),
+		.shared_data_off = offsetof(PgStatShared_VacuumRelation, stats),
+		.shared_data_len = sizeof(((PgStatShared_VacuumRelation *) 0)->stats),
+		.pending_size = sizeof(PgStat_RelationVacuumPending),
+
+		.flush_pending_cb = pgstat_vacuum_relation_flush_cb
+	},
 };
 
 /*
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 65207d30378..80e6c7c229a 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -46,6 +46,15 @@ pgstat_drop_database(Oid databaseid)
 	pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
 }
 
+/*
+ * Remove entry for the database being dropped.
+ */
+void
+pgstat_drop_vacuum_database(Oid databaseid)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_VACUUM_DB, databaseid, InvalidOid);
+}
+
 /*
  * Called from autovacuum.c to report startup of an autovacuum process.
  * We are called before InitPostgres is done, so can't rely on MyDatabaseId;
@@ -485,7 +494,6 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	pgstat_unlock_entry(entry_ref);
 
 	memset(pendingent, 0, sizeof(*pendingent));
-	memset(&(pendingent)->vacuum_ext, 0, sizeof(ExtVacReport));
 
 	return true;
 }
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 2675c541369..e8665d23099 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -47,8 +47,6 @@ static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_lev
 static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
 static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
 static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
-static void pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
-										   bool accumulate_reltype_specific_info);
 
 
 /*
@@ -210,12 +208,11 @@ pgstat_drop_relation(Relation rel)
  */
 void
 pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
-					 PgStat_Counter deadtuples, TimestampTz starttime, ExtVacReport * params)
+					 PgStat_Counter deadtuples, TimestampTz starttime)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Relation *shtabentry;
 	PgStat_StatTabEntry *tabentry;
-	PgStatShared_Database *dbentry;
 	Oid			dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
 	TimestampTz ts;
 	PgStat_Counter elapsedtime;
@@ -237,8 +234,6 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
 	tabentry->live_tuples = livetuples;
 	tabentry->dead_tuples = deadtuples;
 
-	pgstat_accumulate_extvac_stats(&tabentry->vacuum_ext, params, true);
-
 	/*
 	 * It is quite possible that a non-aggressive VACUUM ended up skipping
 	 * various pages, however, we'll zero the insert counter here regardless.
@@ -274,16 +269,6 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
 	 */
 	pgstat_flush_io(false);
 	(void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
-
-	if (dboid != InvalidOid)
-	{
-		entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
-												dboid, InvalidOid, false);
-		dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
-
-		pgstat_accumulate_extvac_stats(&dbentry->stats.vacuum_ext, params, false);
-		pgstat_unlock_entry(entry_ref);
-	}
 }
 
 /*
@@ -918,6 +903,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	return true;
 }
 
+void
+pgstat_vacuum_relation_delete_pending_cb(Oid relid)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_VACUUM_RELATION, relid, InvalidOid);
+}
+
 void
 pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
 {
@@ -1027,55 +1018,3 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
 		trans->tuples_deleted = trans->deleted_pre_truncdrop;
 	}
 }
-
-static void
-pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
-							   bool accumulate_reltype_specific_info)
-{
-	dst->total_blks_read += src->total_blks_read;
-	dst->total_blks_hit += src->total_blks_hit;
-	dst->total_blks_dirtied += src->total_blks_dirtied;
-	dst->total_blks_written += src->total_blks_written;
-	dst->wal_bytes += src->wal_bytes;
-	dst->wal_fpi += src->wal_fpi;
-	dst->wal_records += src->wal_records;
-	dst->blk_read_time += src->blk_read_time;
-	dst->blk_write_time += src->blk_write_time;
-	dst->delay_time += src->delay_time;
-	dst->total_time += src->total_time;
-	dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
-
-	if (!accumulate_reltype_specific_info)
-		return;
-
-	if (dst->type == PGSTAT_EXTVAC_INVALID)
-		dst->type = src->type;
-
-	Assert(src->type == PGSTAT_EXTVAC_INVALID || src->type == dst->type);
-
-	if (dst->type == src->type)
-	{
-		dst->blks_fetched += src->blks_fetched;
-		dst->blks_hit += src->blks_hit;
-
-		if (dst->type == PGSTAT_EXTVAC_TABLE)
-		{
-			dst->table.pages_scanned += src->table.pages_scanned;
-			dst->table.pages_removed += src->table.pages_removed;
-			dst->table.vm_new_frozen_pages += src->table.vm_new_frozen_pages;
-			dst->table.vm_new_visible_pages += src->table.vm_new_visible_pages;
-			dst->table.vm_new_visible_frozen_pages += src->table.vm_new_visible_frozen_pages;
-			dst->tuples_deleted += src->tuples_deleted;
-			dst->table.tuples_frozen += src->table.tuples_frozen;
-			dst->table.recently_dead_tuples += src->table.recently_dead_tuples;
-			dst->table.index_vacuum_count += src->table.index_vacuum_count;
-			dst->table.missed_dead_pages += src->table.missed_dead_pages;
-			dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
-		}
-		else if (dst->type == PGSTAT_EXTVAC_INDEX)
-		{
-			dst->index.pages_deleted += src->index.pages_deleted;
-			dst->tuples_deleted += src->tuples_deleted;
-		}
-	}
-}
diff --git a/src/backend/utils/activity/pgstat_vacuum.c b/src/backend/utils/activity/pgstat_vacuum.c
new file mode 100644
index 00000000000..340ee24f26a
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_vacuum.c
@@ -0,0 +1,214 @@
+#include "postgres.h"
+
+#include "pgstat.h"
+#include "utils/pgstat_internal.h"
+#include "utils/memutils.h"
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool		pgstat_track_vacuum_statistics_for_relations = false;
+
+#define ACCUMULATE_FIELD(field) dst->field += src->field;
+
+#define ACCUMULATE_SUBFIELD(substruct, field) \
+    (dst->substruct.field += src->substruct.field)
+
+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(blks_fetched);
+	ACCUMULATE_FIELD(blks_hit);
+
+	ACCUMULATE_FIELD(wal_records);
+	ACCUMULATE_FIELD(wal_fpi);
+	ACCUMULATE_FIELD(wal_bytes);
+
+	ACCUMULATE_FIELD(blk_read_time);
+	ACCUMULATE_FIELD(blk_write_time);
+	ACCUMULATE_FIELD(delay_time);
+	ACCUMULATE_FIELD(total_time);
+
+	ACCUMULATE_FIELD(tuples_deleted);
+	ACCUMULATE_FIELD(wraparound_failsafe_count);
+}
+
+static void
+pgstat_accumulate_extvac_stats_relations(PgStat_VacuumRelationCounts * dst, PgStat_VacuumRelationCounts * src)
+{
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
+	if (dst->type == PGSTAT_EXTVAC_INVALID)
+		dst->type = src->type;
+
+	Assert(src->type != PGSTAT_EXTVAC_INVALID && src->type != PGSTAT_EXTVAC_DB && src->type == dst->type);
+
+	pgstat_accumulate_common(&dst->common, &src->common);
+
+	ACCUMULATE_SUBFIELD(common, blks_fetched);
+	ACCUMULATE_SUBFIELD(common, blks_hit);
+
+	if (dst->type == PGSTAT_EXTVAC_TABLE)
+	{
+		ACCUMULATE_SUBFIELD(common, tuples_deleted);
+		ACCUMULATE_SUBFIELD(table, pages_scanned);
+		ACCUMULATE_SUBFIELD(table, pages_removed);
+		ACCUMULATE_SUBFIELD(table, vm_new_frozen_pages);
+		ACCUMULATE_SUBFIELD(table, vm_new_visible_pages);
+		ACCUMULATE_SUBFIELD(table, vm_new_visible_frozen_pages);
+		ACCUMULATE_SUBFIELD(table, tuples_frozen);
+		ACCUMULATE_SUBFIELD(table, recently_dead_tuples);
+		ACCUMULATE_SUBFIELD(table, index_vacuum_count);
+		ACCUMULATE_SUBFIELD(table, missed_dead_pages);
+		ACCUMULATE_SUBFIELD(table, missed_dead_tuples);
+	}
+	else if (dst->type == PGSTAT_EXTVAC_INDEX)
+	{
+		ACCUMULATE_SUBFIELD(common, tuples_deleted);
+		ACCUMULATE_SUBFIELD(index, pages_deleted);
+	}
+}
+
+static void
+pgstat_accumulate_extvac_stats_db(PgStat_VacuumDBCounts * dst, PgStat_VacuumDBCounts * src)
+{
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
+	pgstat_accumulate_common(&dst->common, &src->common);
+}
+
+/*
+ * Report that the table was just vacuumed and flush statistics.
+ */
+void
+pgstat_report_vacuum_extstats(Oid tableoid, bool shared,
+							  PgStat_VacuumRelationCounts * params)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_VacuumRelation *shtabentry;
+	PgStatShared_VacuumDB *shdbentry;
+	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
+
+	if (!pgstat_track_vacuum_statistics)
+		return;
+
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_VACUUM_RELATION,
+											dboid, tableoid, false);
+	shtabentry = (PgStatShared_VacuumRelation *) entry_ref->shared_stats;
+	pgstat_accumulate_extvac_stats_relations(&shtabentry->stats, params);
+
+	pgstat_unlock_entry(entry_ref);
+
+
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_VACUUM_DB,
+											dboid, InvalidOid, false);
+
+	shdbentry = (PgStatShared_VacuumDB *) entry_ref->shared_stats;
+
+	pgstat_accumulate_common(&shdbentry->stats.common, &params->common);
+
+	pgstat_unlock_entry(entry_ref);
+}
+
+/*
+ * Flush out pending stats for the entry
+ *
+ * If nowait is true, this function returns false if lock could not
+ * immediately acquired, otherwise true is returned.
+ */
+bool
+pgstat_vacuum_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+	PgStatShared_VacuumRelation *shtabstats;
+	PgStat_RelationVacuumPending *pendingent;	/* table entry of shared stats */
+
+	pendingent = (PgStat_RelationVacuumPending *) entry_ref->pending;
+	shtabstats = (PgStatShared_VacuumRelation *) entry_ref->shared_stats;
+
+	/*
+	 * Ignore entries that didn't accumulate any actual counts.
+	 */
+	if (pg_memory_is_all_zeros(&pendingent,
+							   sizeof(struct PgStat_RelationVacuumPending)))
+		return true;
+
+	if (!pgstat_lock_entry(entry_ref, nowait))
+	{
+		return false;
+	}
+
+	pgstat_accumulate_extvac_stats_relations(&(shtabstats->stats), &(pendingent->counts));
+
+	pgstat_unlock_entry(entry_ref);
+
+	return true;
+}
+
+/*
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * the vacuum collected statistics for one relation or NULL.
+ */
+PgStat_VacuumRelationCounts *
+pgstat_fetch_stat_vacuum_tabentry(Oid relid, Oid dbid)
+{
+	return (PgStat_VacuumRelationCounts *)
+		pgstat_fetch_entry(PGSTAT_KIND_VACUUM_RELATION, dbid, relid);
+}
+
+PgStat_VacuumDBCounts *
+pgstat_fetch_stat_vacuum_dbentry(Oid dbid)
+{
+	return (PgStat_VacuumDBCounts *)
+		pgstat_fetch_entry(PGSTAT_KIND_VACUUM_DB, dbid, InvalidOid);
+}
+
+bool
+pgstat_vacuum_db_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+	PgStatShared_VacuumDB *sharedent;
+	PgStat_VacuumDBCounts *pendingent;
+
+	pendingent = (PgStat_VacuumDBCounts *) entry_ref->pending;
+	sharedent = (PgStatShared_VacuumDB *) entry_ref->shared_stats;
+
+	if (!pgstat_lock_entry(entry_ref, nowait))
+		return false;
+
+	/* The entry was successfully flushed, add the same to database stats */
+	pgstat_accumulate_extvac_stats_db(&(sharedent->stats), pendingent);
+
+	pgstat_unlock_entry(entry_ref);
+
+	return true;
+}
+
+/*
+ * Find or create a local PgStat_VacuumDBCounts entry for dboid.
+ */
+PgStat_VacuumDBCounts *
+pgstat_prep_vacuum_database_pending(Oid dboid)
+{
+	PgStat_EntryRef *entry_ref;
+
+	/*
+	 * This should not report stats on database objects before having
+	 * connected to a database.
+	 */
+	Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_VACUUM_DB, dboid, InvalidOid,
+										  NULL);
+
+	if (entry_ref == NULL)
+		return NULL;
+
+	return entry_ref->pending;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 4e2714f2e6a..0a64f034a3f 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2314,7 +2314,6 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objid));
 }
 
-
 /*
  * Get the vacuum statistics for the heap tables.
  */
@@ -2324,41 +2323,45 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
 #define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 26
 
 	Oid			relid = PG_GETARG_OID(0);
-	PgStat_StatTabEntry *tabentry;
-	ExtVacReport *extvacuum;
+	PgStat_VacuumRelationCounts *extvacuum;
+	PgStat_VacuumRelationCounts *pending;
 	TupleDesc	tupdesc;
 	Datum		values[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
 	bool		nulls[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
 	char		buf[256];
 	int			i = 0;
 
+	/* Build a tuple descriptor for our result type */
 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 		elog(ERROR, "return type must be a row type");
 
-	tabentry = pgstat_fetch_stat_tabentry(relid);
+	pending = pgstat_fetch_stat_vacuum_tabentry(relid, MyDatabaseId);
 
-	if (!tabentry)
-	{
-		InitMaterializedSRF(fcinfo, 0);
-		PG_RETURN_VOID();
-	}
-	else
+	if (!pending)
 	{
-		extvacuum = &(tabentry->vacuum_ext);
+		pending = pgstat_fetch_stat_vacuum_tabentry(relid, 0);
+
+		if (!pending)
+		{
+			InitMaterializedSRF(fcinfo, 0);
+			PG_RETURN_VOID();
+		}
 	}
 
+	extvacuum = pending;
+
 	i = 0;
 
 	values[i++] = ObjectIdGetDatum(relid);
 
-	values[i++] = Int64GetDatum(extvacuum->total_blks_read);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+	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->blks_fetched -
-								extvacuum->blks_hit);
-	values[i++] = Int64GetDatum(extvacuum->blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.blks_fetched -
+								extvacuum->common.blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.blks_hit);
 
 	values[i++] = Int64GetDatum(extvacuum->table.pages_scanned);
 	values[i++] = Int64GetDatum(extvacuum->table.pages_removed);
@@ -2366,28 +2369,28 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
 	values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_pages);
 	values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_frozen_pages);
 	values[i++] = Int64GetDatum(extvacuum->table.missed_dead_pages);
-	values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+	values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
 	values[i++] = Int64GetDatum(extvacuum->table.tuples_frozen);
 	values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
 	values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
 
-	values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+	values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
 	values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
 
-	values[i++] = Int64GetDatum(extvacuum->wal_records);
-	values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+	values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+	values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
 
 	/* Convert to numeric, like pg_stat_statements */
-	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
 	values[i++] = DirectFunctionCall3(numeric_in,
 									  CStringGetDatum(buf),
 									  ObjectIdGetDatum(0),
 									  Int32GetDatum(-1));
 
-	values[i++] = Float8GetDatum(extvacuum->blk_read_time);
-	values[i++] = Float8GetDatum(extvacuum->blk_write_time);
-	values[i++] = Float8GetDatum(extvacuum->delay_time);
-	values[i++] = Float8GetDatum(extvacuum->total_time);
+	values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+	values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+	values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+	values[i++] = Float8GetDatum(extvacuum->common.total_time);
 
 	Assert(i == PG_STAT_GET_VACUUM_TABLES_STATS_COLS);
 
@@ -2404,8 +2407,8 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
 #define PG_STAT_GET_VACUUM_INDEX_STATS_COLS	16
 
 	Oid			relid = PG_GETARG_OID(0);
-	PgStat_StatTabEntry *tabentry;
-	ExtVacReport *extvacuum;
+	PgStat_VacuumRelationCounts *extvacuum;
+	PgStat_VacuumRelationCounts *pending;
 	TupleDesc	tupdesc;
 	Datum		values[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
 	bool		nulls[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
@@ -2415,48 +2418,51 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 		elog(ERROR, "return type must be a row type");
 
-	tabentry = pgstat_fetch_stat_tabentry(relid);
+	pending = pgstat_fetch_stat_vacuum_tabentry(relid, MyDatabaseId);
 
-	if (tabentry == NULL)
-	{
-		InitMaterializedSRF(fcinfo, 0);
-		PG_RETURN_VOID();
-	}
-	else
+	if (!pending)
 	{
-		extvacuum = &(tabentry->vacuum_ext);
+		pending = pgstat_fetch_stat_vacuum_tabentry(relid, 0);
+
+		if (!pending)
+		{
+			InitMaterializedSRF(fcinfo, 0);
+			PG_RETURN_VOID();
+		}
 	}
 
+	extvacuum = pending;
+
 	i = 0;
 
 	values[i++] = ObjectIdGetDatum(relid);
 
-	values[i++] = Int64GetDatum(extvacuum->total_blks_read);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+	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->blks_fetched -
-								extvacuum->blks_hit);
-	values[i++] = Int64GetDatum(extvacuum->blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.blks_fetched -
+								extvacuum->common.blks_hit);
+	values[i++] = Int64GetDatum(extvacuum->common.blks_hit);
 
 	values[i++] = Int64GetDatum(extvacuum->index.pages_deleted);
-	values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+	values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
 
-	values[i++] = Int64GetDatum(extvacuum->wal_records);
-	values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+	values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+	values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
 
 	/* Convert to numeric, like pg_stat_statements */
-	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
 	values[i++] = DirectFunctionCall3(numeric_in,
 									  CStringGetDatum(buf),
 									  ObjectIdGetDatum(0),
 									  Int32GetDatum(-1));
 
-	values[i++] = Float8GetDatum(extvacuum->blk_read_time);
-	values[i++] = Float8GetDatum(extvacuum->blk_write_time);
-	values[i++] = Float8GetDatum(extvacuum->delay_time);
-	values[i++] = Float8GetDatum(extvacuum->total_time);
+	values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+	values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+	values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+	values[i++] = Float8GetDatum(extvacuum->common.total_time);
 
 	Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS);
 
@@ -2470,8 +2476,8 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
 #define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS	14
 
 	Oid			dbid = PG_GETARG_OID(0);
-	PgStat_StatDBEntry *dbentry;
-	ExtVacReport *extvacuum;
+	PgStat_VacuumDBCounts *extvacuum;
+	PgStat_VacuumDBCounts *pending;
 	TupleDesc	tupdesc;
 	Datum		values[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
 	bool		nulls[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
@@ -2481,42 +2487,41 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 		elog(ERROR, "return type must be a row type");
 
-	dbentry = pgstat_fetch_stat_dbentry(dbid);
+	pending = pgstat_fetch_stat_vacuum_dbentry(dbid);
 
-	if (dbentry == NULL)
+	if (!pending)
 	{
 		InitMaterializedSRF(fcinfo, 0);
 		PG_RETURN_VOID();
 	}
-	else
-	{
-		extvacuum = &(dbentry->vacuum_ext);
-	}
+
+	extvacuum = pending;
 
 	i = 0;
 
 	values[i++] = ObjectIdGetDatum(dbid);
 
-	values[i++] = Int64GetDatum(extvacuum->total_blks_read);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
-	values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+	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->wal_records);
-	values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+	values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+	values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
 
 	/* Convert to numeric, like pg_stat_statements */
-	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
 	values[i++] = DirectFunctionCall3(numeric_in,
 									  CStringGetDatum(buf),
 									  ObjectIdGetDatum(0),
 									  Int32GetDatum(-1));
 
-	values[i++] = Float8GetDatum(extvacuum->blk_read_time);
-	values[i++] = Float8GetDatum(extvacuum->blk_write_time);
-	values[i++] = Float8GetDatum(extvacuum->delay_time);
-	values[i++] = Float8GetDatum(extvacuum->total_time);
-	values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+	values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+	values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+	values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+	values[i++] = Float8GetDatum(extvacuum->common.total_time);
+	values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
+	values[i++] = Int32GetDatum(extvacuum->errors);
 
 	Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
 
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..631df3a57c3 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3084,6 +3084,12 @@
   boot_val => 'false',
 },
 
+{ name => 'track_vacuum_statistics', type => 'bool', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
+  short_desc => 'Collects vacuum statistics for vacuum activity.',
+  variable => 'pgstat_track_vacuum_statistics',
+  boot_val => 'false',
+},
+
 { name => 'track_wal_io_timing', type => 'bool', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
   short_desc => 'Collects timing statistics for WAL I/O activity.',
   variable => 'track_wal_io_timing',
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index b48ace6084b..6e85b08aa89 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -437,5 +437,5 @@ extern double anl_get_next_S(double t, int n, double *stateptr);
 extern void extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
 								   LVExtStatCountersIdx * counters);
 extern void extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
-								 LVExtStatCountersIdx * counters, ExtVacReport * report);
+								 LVExtStatCountersIdx * counters, PgStat_VacuumRelationCounts * report);
 #endif							/* VACUUM_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f3bdc1c38df..61d488f1bf8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -119,54 +119,100 @@ typedef enum ExtVacReportType
 {
 	PGSTAT_EXTVAC_INVALID = 0,
 	PGSTAT_EXTVAC_TABLE = 1,
-	PGSTAT_EXTVAC_INDEX = 2
-} ExtVacReportType;
+	PGSTAT_EXTVAC_INDEX = 2,
+	PGSTAT_EXTVAC_DB = 3,
+}			ExtVacReportType;
 
 /* ----------
+ * PgStat_TableCounts			The actual per-table counts kept by a backend
  *
- * ExtVacReport
+ * This struct should contain only actual event counters, because we make use
+ * of pg_memory_is_all_zeros() to detect whether there are any stats updates
+ * to apply.
  *
- * Additional statistics of vacuum processing over a relation.
- * pages_removed is the amount by which the physically shrank,
- * if any (ie the change in its total size on disk)
- * pages_deleted refer to free space within the index file
+ * It is a component of PgStat_TableStatus (within-backend state).
+ *
+ * Note: for a table, tuples_returned is the number of tuples successfully
+ * fetched by heap_getnext, while tuples_fetched is the number of tuples
+ * successfully fetched by heap_fetch under the control of bitmap indexscans.
+ * For an index, tuples_returned is the number of index entries returned by
+ * the index AM, while tuples_fetched is the number of tuples successfully
+ * fetched by heap_fetch under the control of simple indexscans for this index.
+ *
+ * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted
+ * actions, regardless of whether the transaction committed.  delta_live_tuples,
+ * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
+ * Note that delta_live_tuples and delta_dead_tuples can be negative!
  * ----------
  */
-typedef struct ExtVacReport
+typedef struct PgStat_TableCounts
 {
-	/*
-	 * number of blocks missed, hit, dirtied and written during a vacuum of
-	 * specific relation
-	 */
+	PgStat_Counter numscans;
+
+	PgStat_Counter tuples_returned;
+	PgStat_Counter tuples_fetched;
+
+	PgStat_Counter tuples_inserted;
+	PgStat_Counter tuples_updated;
+	PgStat_Counter tuples_deleted;
+	PgStat_Counter tuples_hot_updated;
+	PgStat_Counter tuples_newpage_updated;
+	bool		truncdropped;
+
+	PgStat_Counter delta_live_tuples;
+	PgStat_Counter delta_dead_tuples;
+	PgStat_Counter changed_tuples;
+
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
+
+	PgStat_Counter rev_all_visible_pages;
+	PgStat_Counter rev_all_frozen_pages;
+} PgStat_TableCounts;
+
+typedef struct PgStat_CommonCounts
+{
+	/* blocks */
 	int64		total_blks_read;
 	int64		total_blks_hit;
 	int64		total_blks_dirtied;
 	int64		total_blks_written;
 
-	/*
-	 * blocks missed and hit for just the heap during a vacuum of specific
-	 * relation
-	 */
+	/* heap blocks */
 	int64		blks_fetched;
 	int64		blks_hit;
 
-	/* Vacuum WAL usage stats */
-	int64		wal_records;	/* wal usage: number of WAL records */
-	int64		wal_fpi;		/* wal usage: number of WAL full page images
-								 * produced */
-	uint64		wal_bytes;		/* wal usage: size of WAL records produced */
+	/* WAL */
+	int64		wal_records;
+	int64		wal_fpi;
+	uint64		wal_bytes;
 
-	/* Time stats. */
-	double		blk_read_time;	/* time spent reading pages, in msec */
-	double		blk_write_time; /* time spent writing pages, in msec */
-	double		delay_time;		/* how long vacuum slept in vacuum delay
-								 * point, in msec */
-	double		total_time;		/* total time of a vacuum operation, in msec */
+	/* Time */
+	double		blk_read_time;
+	double		blk_write_time;
+	double		delay_time;
+	double		total_time;
 
-	int64		tuples_deleted; /* tuples deleted by vacuum */
+	/* tuples */
+	int64		tuples_deleted;
 
-	int32		wraparound_failsafe_count;	/* the number of times to prevent
-											 * wraparound problem */
+	/* failsafe */
+	int32		wraparound_failsafe_count;
+}			PgStat_CommonCounts;
+
+/* ----------
+ *
+ * PgStat_VacuumRelationCounts
+ *
+ * Additional statistics of vacuum processing over a relation.
+ * pages_removed is the amount by which the physically shrank,
+ * if any (ie the change in its total size on disk)
+ * pages_deleted refer to free space within the index file
+ * ----------
+ */
+typedef struct PgStat_VacuumRelationCounts
+{
+	PgStat_CommonCounts common;
 
 	ExtVacReportType type;		/* heap, index, etc. */
 
@@ -185,6 +231,13 @@ typedef struct ExtVacReport
 	{
 		struct
 		{
+			int64		tuples_frozen;	/* tuples frozen up by vacuum */
+			int64		recently_dead_tuples;	/* deleted tuples that are
+												 * still visible to some
+												 * transaction */
+			int64		missed_dead_tuples; /* tuples not pruned by vacuum due
+											 * to failure to get a cleanup
+											 * lock */
 			int64		pages_scanned;	/* heap pages examined (not skipped by
 										 * VM) */
 			int64		pages_removed;	/* heap pages removed by vacuum
@@ -192,10 +245,6 @@ typedef struct ExtVacReport
 			int64		pages_frozen;	/* pages marked in VM as frozen */
 			int64		pages_all_visible;	/* pages marked in VM as
 											 * all-visible */
-			int64		tuples_frozen;	/* tuples frozen up by vacuum */
-			int64		recently_dead_tuples;	/* deleted tuples that are
-												 * still visible to some
-												 * transaction */
 			int64		vm_new_frozen_pages;	/* pages marked in VM as
 												 * frozen */
 			int64		vm_new_visible_pages;	/* pages marked in VM as
@@ -203,9 +252,6 @@ typedef struct ExtVacReport
 			int64		vm_new_visible_frozen_pages;	/* pages marked in VM as
 														 * all-visible and
 														 * frozen */
-			int64		missed_dead_tuples; /* tuples not pruned by vacuum due
-											 * to failure to get a cleanup
-											 * lock */
 			int64		missed_dead_pages;	/* pages with missed dead tuples */
 			int64		index_vacuum_count; /* number of index vacuumings */
 		}			table;
@@ -214,60 +260,21 @@ typedef struct ExtVacReport
 			int64		pages_deleted;	/* number of pages deleted by vacuum */
 		}			index;
 	} /* per_type_stats */ ;
-}			ExtVacReport;
+}			PgStat_VacuumRelationCounts;
 
-/* ----------
- * PgStat_TableCounts			The actual per-table counts kept by a backend
- *
- * This struct should contain only actual event counters, because we make use
- * of pg_memory_is_all_zeros() to detect whether there are any stats updates
- * to apply.
- *
- * It is a component of PgStat_TableStatus (within-backend state).
- *
- * Note: for a table, tuples_returned is the number of tuples successfully
- * fetched by heap_getnext, while tuples_fetched is the number of tuples
- * successfully fetched by heap_fetch under the control of bitmap indexscans.
- * For an index, tuples_returned is the number of index entries returned by
- * the index AM, while tuples_fetched is the number of tuples successfully
- * fetched by heap_fetch under the control of simple indexscans for this index.
- *
- * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted
- * actions, regardless of whether the transaction committed.  delta_live_tuples,
- * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
- * Note that delta_live_tuples and delta_dead_tuples can be negative!
- * ----------
- */
-typedef struct PgStat_TableCounts
+typedef struct PgStat_VacuumRelationStatus
 {
-	PgStat_Counter numscans;
-
-	PgStat_Counter tuples_returned;
-	PgStat_Counter tuples_fetched;
-
-	PgStat_Counter tuples_inserted;
-	PgStat_Counter tuples_updated;
-	PgStat_Counter tuples_deleted;
-	PgStat_Counter tuples_hot_updated;
-	PgStat_Counter tuples_newpage_updated;
-	bool		truncdropped;
-
-	PgStat_Counter delta_live_tuples;
-	PgStat_Counter delta_dead_tuples;
-	PgStat_Counter changed_tuples;
-
-	PgStat_Counter blocks_fetched;
-	PgStat_Counter blocks_hit;
-
-	PgStat_Counter rev_all_visible_pages;
-	PgStat_Counter rev_all_frozen_pages;
+	Oid			id;				/* table's OID */
+	bool		shared;			/* is it a shared catalog? */
+	PgStat_VacuumRelationCounts counts; /* event counts to be sent */
+}			PgStat_VacuumRelationStatus;
 
-	/*
-	 * Additional cumulative stat on vacuum operations. Use an expensive
-	 * structure as an abstraction for different types of relations.
-	 */
-	ExtVacReport vacuum_ext;
-} PgStat_TableCounts;
+typedef struct PgStat_VacuumDBCounts
+{
+	Oid			dbjid;
+	PgStat_CommonCounts common;
+	int32		errors;
+}			PgStat_VacuumDBCounts;
 
 /* ----------
  * PgStat_TableStatus			Per-table status within a backend
@@ -293,6 +300,12 @@ typedef struct PgStat_TableStatus
 	Relation	relation;		/* rel that is using this entry */
 } PgStat_TableStatus;
 
+typedef struct PgStat_RelationVacuumPending
+{
+	Oid			id;				/* table's OID */
+	PgStat_VacuumRelationCounts counts; /* event counts to be sent */
+}			PgStat_RelationVacuumPending;
+
 /* ----------
  * PgStat_TableXactStatus		Per-table, per-subtransaction status
  * ----------
@@ -489,8 +502,6 @@ typedef struct PgStat_StatDBEntry
 	PgStat_Counter parallel_workers_launched;
 
 	TimestampTz stat_reset_timestamp;
-
-	ExtVacReport vacuum_ext;	/* extended vacuum statistics */
 } PgStat_StatDBEntry;
 
 typedef struct PgStat_StatFuncEntry
@@ -578,8 +589,6 @@ typedef struct PgStat_StatTabEntry
 
 	PgStat_Counter rev_all_visible_pages;
 	PgStat_Counter rev_all_frozen_pages;
-
-	ExtVacReport vacuum_ext;
 } PgStat_StatTabEntry;
 
 /* ------
@@ -788,7 +797,7 @@ extern void pgstat_unlink_relation(Relation rel);
 
 extern void pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
 								 PgStat_Counter deadtuples,
-								 TimestampTz starttime, ExtVacReport * params);
+								 TimestampTz starttime);
 extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter, TimestampTz starttime);
@@ -924,6 +933,16 @@ extern int	pgstat_get_transactional_drops(bool isCommit, struct xl_xact_stats_it
 extern void pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, bool is_redo);
 
 
+extern void pgstat_drop_vacuum_database(Oid databaseid);
+extern void pgstat_vacuum_relation_delete_pending_cb(Oid relid);
+extern void
+			pgstat_report_vacuum_extstats(Oid tableoid, bool shared,
+										  PgStat_VacuumRelationCounts * params);
+extern PgStat_RelationVacuumPending * find_vacuum_relation_entry(Oid relid);
+extern PgStat_VacuumDBCounts * pgstat_prep_vacuum_database_pending(Oid dboid);
+extern PgStat_VacuumRelationCounts * pgstat_fetch_stat_vacuum_tabentry(Oid relid, Oid dbid);
+PgStat_VacuumDBCounts *pgstat_fetch_stat_vacuum_dbentry(Oid dbid);
+
 /*
  * Functions in pgstat_wal.c
  */
@@ -940,7 +959,8 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void);
 extern PGDLLIMPORT bool pgstat_track_counts;
 extern PGDLLIMPORT int pgstat_track_functions;
 extern PGDLLIMPORT int pgstat_fetch_consistency;
-
+extern PGDLLIMPORT bool pgstat_track_vacuum_statistics;
+extern PGDLLIMPORT bool pgstat_track_vacuum_statistics_for_relations;
 
 /*
  * Variables in pgstat_bgwriter.c
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 7dffab8dbdd..4abe70cb54e 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -500,6 +500,18 @@ typedef struct PgStatShared_Relation
 	PgStat_StatTabEntry stats;
 } PgStatShared_Relation;
 
+typedef struct PgStatShared_VacuumDB
+{
+	PgStatShared_Common header;
+	PgStat_VacuumDBCounts stats;
+}			PgStatShared_VacuumDB;
+
+typedef struct PgStatShared_VacuumRelation
+{
+	PgStatShared_Common header;
+	PgStat_VacuumRelationCounts stats;
+}			PgStatShared_VacuumRelation;
+
 typedef struct PgStatShared_Function
 {
 	PgStatShared_Common header;
@@ -678,6 +690,9 @@ extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind,
 extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid);
 extern void pgstat_snapshot_fixed(PgStat_Kind kind);
 
+bool		pgstat_vacuum_db_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern bool pgstat_vacuum_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+
 
 /*
  * Functions in pgstat_archiver.c
diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h
index eb5f0b3ae6d..52e884fbf8b 100644
--- a/src/include/utils/pgstat_kind.h
+++ b/src/include/utils/pgstat_kind.h
@@ -38,9 +38,11 @@
 #define PGSTAT_KIND_IO	10
 #define PGSTAT_KIND_SLRU	11
 #define PGSTAT_KIND_WAL	12
+#define PGSTAT_KIND_VACUUM_DB	13
+#define PGSTAT_KIND_VACUUM_RELATION	14
 
 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
-#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
+#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_VACUUM_RELATION
 #define PGSTAT_KIND_BUILTIN_SIZE (PGSTAT_KIND_BUILTIN_MAX + 1)
 
 /* Custom stats kinds */
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index bd3cb544e30..e2fd541fd89 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -28,6 +28,7 @@ $node->init;
 # Configure the server logging level for the test
 $node->append_conf('postgresql.conf', q{
     log_min_messages = notice
+    track_vacuum_statistics = on
 });
 
 my $stderr;
@@ -64,8 +65,9 @@ $node->safe_psql($dbname, q{
 
 $node->safe_psql(
     $dbname,
-    "CREATE TABLE vestat (x int PRIMARY KEY)
+    "CREATE TABLE vestat (x int)
          WITH (autovacuum_enabled = off, fillfactor = 10);
+     create index vestat_pkey on vestat (x);
      INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
      ANALYZE vestat;"
 );
@@ -115,7 +117,7 @@ sub wait_for_vacuum_stats {
                 AND
                 (SELECT (tuples_deleted > $idx_tuples_deleted AND wal_records > $idx_wal_records)
                   FROM pg_stat_vacuum_indexes
-                  WHERE relname = 'vestat_pkey');"
+                  WHERE indexrelname = 'vestat_pkey');"
         );
 
         return 1 if ($result_query eq 't');
@@ -183,7 +185,7 @@ sub fetch_vacuum_stats {
         $dbname,
         "SELECT tuples_deleted, pages_deleted, wal_records, wal_bytes, wal_fpi
            FROM pg_stat_vacuum_indexes
-          WHERE relname = 'vestat_pkey';"
+          WHERE indexrelname = 'vestat_pkey';"
     );
 
     $index_base_statistics =~ s/\s*\|\s*/ /g;   # transform " | " into space
@@ -321,7 +323,7 @@ sub fetch_error_base_idx_vacuum_statistics {
     $dbname,
     "SELECT tuples_deleted, pages_deleted
        FROM pg_stat_vacuum_indexes
-      WHERE relname = 'vestat_pkey';"
+      WHERE indexrelname = 'vestat_pkey';"
     );
     $base_statistics =~ s/\s*\|\s*/ /g;   # transform " | " in space
     my ($cur_tuples_deleted, $cur_pages_deleted) = split /\s+/, $base_statistics;
@@ -343,7 +345,7 @@ sub fetch_error_wal_idx_vacuum_statistics {
         $dbname,
         "SELECT wal_records, wal_bytes, wal_fpi
         FROM pg_stat_vacuum_indexes
-        WHERE relname = 'vestat_pkey';"
+        WHERE indexrelname = 'vestat_pkey';"
     );
 
     $wal_raw =~ s/\s*\|\s*/ /g;   # transform " | " in space
@@ -707,7 +709,7 @@ $base_stats = $node->safe_psql(
     'postgres',
     "SELECT count(*) = 0
      FROM pg_stat_vacuum_indexes
-     WHERE relname = 'vestat_pkey';"
+     WHERE indexrelname = 'vestat_pkey';"
 );
 ok($base_stats eq 't', 'check the printing index vacuum extended statistics from another database are not available');
 
@@ -742,6 +744,9 @@ $reloid = $node->safe_psql(
     }
 );
 
+# Run VACUUM on shared table to ensure stats entry is created
+$node->safe_psql($dbname, "VACUUM pg_shdepend;");
+
 # Check if we can get vacuum statistics for cluster relations (dbid = 0)
 $base_stats = $node->safe_psql(
     $dbname,
@@ -760,6 +765,10 @@ my $indoid = $node->safe_psql(
     }
 );
 
+# Run VACUUM on shared index to ensure stats entry is created
+# Note: VACUUM on the table will also vacuum its indexes
+$node->safe_psql($dbname, "VACUUM pg_shdepend;");
+
 $base_stats = $node->safe_psql(
     $dbname,
     qq{
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
index 7528f20098b..2a1c506a22f 100644
--- a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -37,6 +37,7 @@ $node->append_conf('postgresql.conf', q{
 	vacuum_max_eager_freeze_failure_rate = 1.0
 	vacuum_failsafe_age = 0
 	vacuum_multixact_failsafe_age = 0
+  track_vacuum_statistics = on
 });
 
 $node->start();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b627c85e332..4e8b8b8a2b1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,77 +2330,81 @@ pg_stat_user_tables| SELECT relid,
     rev_all_visible_pages
    FROM pg_stat_all_tables
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
-pg_stat_vacuum_database| SELECT db.oid AS dboid,
-    db.datname AS dbname,
-    stats.db_blks_read,
-    stats.db_blks_hit,
-    stats.total_blks_dirtied,
-    stats.total_blks_written,
-    stats.wal_records,
-    stats.wal_fpi,
-    stats.wal_bytes,
-    stats.blk_read_time,
-    stats.blk_write_time,
-    stats.delay_time,
-    stats.total_time,
-    stats.wraparound_failsafe,
-    stats.errors
-   FROM pg_database db,
-    LATERAL pg_stat_get_vacuum_database(db.oid) stats(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
-pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
-    ns.nspname AS schemaname,
-    rel.relname,
-    stats.total_blks_read,
-    stats.total_blks_hit,
-    stats.total_blks_dirtied,
-    stats.total_blks_written,
-    stats.rel_blks_read,
-    stats.rel_blks_hit,
-    stats.pages_deleted,
-    stats.tuples_deleted,
-    stats.wal_records,
-    stats.wal_fpi,
-    stats.wal_bytes,
-    stats.blk_read_time,
-    stats.blk_write_time,
-    stats.delay_time,
-    stats.total_time
-   FROM (pg_class rel
-     JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
-    LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
-  WHERE (rel.relkind = 'i'::"char");
-pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
-    rel.relname,
-    stats.relid,
-    stats.total_blks_read,
-    stats.total_blks_hit,
-    stats.total_blks_dirtied,
-    stats.total_blks_written,
-    stats.rel_blks_read,
-    stats.rel_blks_hit,
-    stats.pages_scanned,
-    stats.pages_removed,
-    stats.vm_new_frozen_pages,
-    stats.vm_new_visible_pages,
-    stats.vm_new_visible_frozen_pages,
-    stats.missed_dead_pages,
-    stats.tuples_deleted,
-    stats.tuples_frozen,
-    stats.recently_dead_tuples,
-    stats.missed_dead_tuples,
-    stats.wraparound_failsafe,
-    stats.index_vacuum_count,
-    stats.wal_records,
-    stats.wal_fpi,
-    stats.wal_bytes,
-    stats.blk_read_time,
-    stats.blk_write_time,
-    stats.delay_time,
-    stats.total_time
-   FROM (pg_class rel
-     JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
-    LATERAL pg_stat_get_vacuum_tables(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
-  WHERE (rel.relkind = 'r'::"char");
+pg_stat_vacuum_database| SELECT d.oid AS dboid,
+    d.datname AS dbname,
+    s.db_blks_read,
+    s.db_blks_hit,
+    s.total_blks_dirtied,
+    s.total_blks_written,
+    s.wal_records,
+    s.wal_fpi,
+    s.wal_bytes,
+    s.blk_read_time,
+    s.blk_write_time,
+    s.delay_time,
+    s.total_time,
+    s.wraparound_failsafe,
+    s.errors
+   FROM pg_database d,
+    LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
+pg_stat_vacuum_indexes| SELECT c.oid AS relid,
+    i.oid AS indexrelid,
+    n.nspname AS schemaname,
+    c.relname,
+    i.relname AS indexrelname,
+    s.total_blks_read,
+    s.total_blks_hit,
+    s.total_blks_dirtied,
+    s.total_blks_written,
+    s.rel_blks_read,
+    s.rel_blks_hit,
+    s.pages_deleted,
+    s.tuples_deleted,
+    s.wal_records,
+    s.wal_fpi,
+    s.wal_bytes,
+    s.blk_read_time,
+    s.blk_write_time,
+    s.delay_time,
+    s.total_time
+   FROM (((pg_class c
+     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, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
+pg_stat_vacuum_tables| SELECT n.nspname AS schemaname,
+    c.relname,
+    s.relid,
+    s.total_blks_read,
+    s.total_blks_hit,
+    s.total_blks_dirtied,
+    s.total_blks_written,
+    s.rel_blks_read,
+    s.rel_blks_hit,
+    s.pages_scanned,
+    s.pages_removed,
+    s.vm_new_frozen_pages,
+    s.vm_new_visible_pages,
+    s.vm_new_visible_frozen_pages,
+    s.missed_dead_pages,
+    s.tuples_deleted,
+    s.tuples_frozen,
+    s.recently_dead_tuples,
+    s.missed_dead_tuples,
+    s.wraparound_failsafe,
+    s.index_vacuum_count,
+    s.wal_records,
+    s.wal_fpi,
+    s.wal_bytes,
+    s.blk_read_time,
+    s.blk_write_time,
+    s.delay_time,
+    s.total_time
+   FROM (pg_class c
+     JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
     wal_bytes,
-- 
2.39.5 (Apple Git-154)