From e19e46e20339b477fbcd4538f0b985386f9b7dfa Mon Sep 17 00:00:00 2001
From: Alena Rybakina <a.rybakina@postgrespro.ru>
Date: Tue, 4 Feb 2025 17:57:44 +0300
Subject: [PATCH 3/4] Machinery for grabbing an extended vacuum statistics on
 databases.

Database vacuum statistics information is the collected general
vacuum statistics indexes and tables owned by the databases, which
they belong to.

In addition to the fact that there are far fewer databases in a system
than relations, vacuum statistics for a database contain fewer statistics
than relations, but they are enough to indicate that something may be
wrong in the system and prompt the administrator to enable extended
monitoring for relations.

So, buffer, wal, statistics of I/O time of read and writen blocks
statistics will be observed because they are collected for both
tables, indexes. In addition, we show the number of errors caught
during operation of the vacuum only for the error level.

wraparound_failsafe_count is a number of times when the vacuum starts
urgent cleanup to prevent wraparound problem which is critical for
the database.

Authors: Alena Rybakina <lena.ribackina@yandex.ru>,
   Andrei Lepikhov <a.lepikhov@postgrespro.ru>,
   Andrei Zubkov <a.zubkov@postgrespro.ru>
Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com>, Masahiko Sawada <sawada.mshk@gmail.com>,
       Ilia Evdokimov <ilya.evdokimov@tantorlabs.com>, jian he <jian.universality@gmail.com>,
       Kirill Reshke <reshkekirill@gmail.com>, Alexander Korotkov <aekorotkov@gmail.com>,
       Jim Nasby <jnasby@upgrade.com>, Sami Imseih <samimseih@gmail.com>
---
 src/backend/access/heap/vacuumlazy.c          |  17 ++-
 src/backend/catalog/system_views.sql          |  27 ++++-
 src/backend/utils/activity/pgstat.c           |   2 +-
 src/backend/utils/activity/pgstat_database.c  |   1 +
 src/backend/utils/activity/pgstat_relation.c  |  46 +++++++-
 src/backend/utils/adt/pgstatfuncs.c           | 100 +++++++++++++++++-
 src/backend/utils/misc/guc_tables.c           |   2 +-
 src/include/catalog/pg_proc.dat               |  13 ++-
 src/include/pgstat.h                          |   5 +-
 .../vacuum-extending-in-repetable-read.spec   |   6 ++
 src/test/regress/expected/rules.out           |  17 +++
 .../expected/vacuum_index_statistics.out      |  16 +--
 ...ut => vacuum_tables_and_db_statistics.out} |  87 +++++++++++++--
 src/test/regress/parallel_schedule            |   2 +-
 .../regress/sql/vacuum_index_statistics.sql   |   6 +-
 ...ql => vacuum_tables_and_db_statistics.sql} |  69 +++++++++++-
 16 files changed, 381 insertions(+), 35 deletions(-)
 rename src/test/regress/expected/{vacuum_tables_statistics.out => vacuum_tables_and_db_statistics.out} (82%)
 rename src/test/regress/sql/{vacuum_tables_statistics.sql => vacuum_tables_and_db_statistics.sql} (81%)

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index f42c700f6bb..1caf3ac3b36 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -660,7 +660,7 @@ accumulate_heap_vacuum_statistics(LVExtStatCounters *extVacCounters, LVRelState
 	vacrel->extVacReport.table.missed_dead_tuples += vacrel->missed_dead_tuples;
 	vacrel->extVacReport.table.missed_dead_pages += vacrel->missed_dead_pages;
 	vacrel->extVacReport.table.index_vacuum_count += vacrel->num_index_scans;
-	vacrel->extVacReport.table.wraparound_failsafe_count += vacrel->wraparound_failsafe_count;
+	vacrel->extVacReport.wraparound_failsafe_count += vacrel->wraparound_failsafe_count;
 }
 
 
@@ -4080,6 +4080,9 @@ vacuum_error_callback(void *arg)
 	switch (errinfo->phase)
 	{
 		case VACUUM_ERRCB_PHASE_SCAN_HEAP:
+			if(geterrelevel() == ERROR)
+					pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_TABLE);
+
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
 				if (OffsetNumberIsValid(errinfo->offnum))
@@ -4095,6 +4098,9 @@ vacuum_error_callback(void *arg)
 			break;
 
 		case VACUUM_ERRCB_PHASE_VACUUM_HEAP:
+			if(geterrelevel() == ERROR)
+				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_TABLE);
+
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
 				if (OffsetNumberIsValid(errinfo->offnum))
@@ -4110,16 +4116,25 @@ vacuum_error_callback(void *arg)
 			break;
 
 		case VACUUM_ERRCB_PHASE_VACUUM_INDEX:
+			if(geterrelevel() == ERROR)
+				pgstat_report_vacuum_error(errinfo->indoid, PGSTAT_EXTVAC_INDEX);
+
 			errcontext("while vacuuming index \"%s\" of relation \"%s.%s\"",
 					   errinfo->indname, errinfo->relnamespace, errinfo->relname);
 			break;
 
 		case VACUUM_ERRCB_PHASE_INDEX_CLEANUP:
+			if(geterrelevel() == ERROR)
+				pgstat_report_vacuum_error(errinfo->indoid, PGSTAT_EXTVAC_INDEX);
+
 			errcontext("while cleaning up index \"%s\" of relation \"%s.%s\"",
 					   errinfo->indname, errinfo->relnamespace, errinfo->relname);
 			break;
 
 		case VACUUM_ERRCB_PHASE_TRUNCATE:
+			if(geterrelevel() == ERROR)
+				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_TABLE);
+
 			if (BlockNumberIsValid(errinfo->blkno))
 				errcontext("while truncating relation \"%s.%s\" to %u blocks",
 						   errinfo->relnamespace, errinfo->relname, errinfo->blkno);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b4187e5ad54..0f8346d7b3c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1493,4 +1493,29 @@ 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';
\ No newline at end of file
+WHERE rel.relkind = 'i';
+
+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,
+  stats.errors AS errors
+FROM
+  pg_database db,
+  LATERAL pg_stat_get_vacuum_database(db.oid) stats;
\ No newline at end of file
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index f5f75aa4264..85557736a3a 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 = true;
+bool		pgstat_track_vacuum_statistics = false;
 
 /* ----------
  * state shared with pgstat_*.c
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index b31f20d41bc..65207d30378 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -485,6 +485,7 @@ 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 9ee03509490..1695680ea62 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -205,6 +205,38 @@ pgstat_drop_relation(Relation rel)
 	}
 }
 
+/* ---------
+ * pgstat_report_vacuum_error() -
+ *
+ *	Tell the collector about an (auto)vacuum interruption.
+ * ---------
+ */
+void
+pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_Relation *shtabentry;
+	PgStat_StatTabEntry *tabentry;
+	Oid			dboid =  MyDatabaseId;
+	PgStat_StatDBEntry *dbentry;	/* pending database entry */
+
+	if (!pgstat_track_counts)
+		return;
+
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
+											dboid, tableoid, false);
+
+	shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
+	tabentry = &shtabentry->stats;
+
+	tabentry->vacuum_ext.type = m_type;
+	pgstat_unlock_entry(entry_ref);
+
+	dbentry = pgstat_prep_database_pending(dboid);
+	dbentry->vacuum_ext.errors++;
+	dbentry->vacuum_ext.type = m_type;
+}
+
 /*
  * Report that the table was just vacuumed and flush IO statistics.
  */
@@ -216,6 +248,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Relation *shtabentry;
 	PgStat_StatTabEntry *tabentry;
+	PgStatShared_Database *dbentry;
 	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
 	TimestampTz ts;
 	PgStat_Counter elapsedtime;
@@ -274,6 +307,16 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	 */
 	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);
+	}
 }
 
 /*
@@ -1030,6 +1073,8 @@ pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,
 	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;
+	dst->errors += src->errors;
 
 	if (!accumulate_reltype_specific_info)
 		return;
@@ -1057,7 +1102,6 @@ pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,
 			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;
-			dst->table.wraparound_failsafe_count += src->table.wraparound_failsafe_count;
 		}
 		else if (dst->type == PGSTAT_EXTVAC_INDEX)
 		{
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 15fa3de0871..c2acdcf0e0e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2383,7 +2383,7 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
 	values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
 	values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
 
-	values[i++] = Int32GetDatum(extvacuum->table.wraparound_failsafe_count);
+	values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
 	values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
 
 	values[i++] = Int64GetDatum(extvacuum->wal_records);
@@ -2513,6 +2513,104 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
 
 	Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS);
 
+	/* Returns the record as Datum */
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
+
+Datum
+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;
+	TupleDesc				 tupdesc;
+	Datum					 values[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
+	bool					 nulls[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
+	char					 buf[256];
+	int						 i = 0;
+	ExtVacReport allzero;
+
+	/* Initialise attributes information in the tuple descriptor */
+	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
+
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "dbid",
+					   INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_ blks_read",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_blks_hit",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_blks_dirtied",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_blks_written",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wal_records",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wal_fpi",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wal_bytes",
+					   NUMERICOID, -1, 0);
+
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "blk_read_time",
+					   FLOAT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "blk_write_time",
+					   FLOAT8OID, -1, 0);
+
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "delay_time",
+					   FLOAT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_time",
+					   FLOAT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wraparound_failsafe_count",
+					   INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "errors",
+					   INT4OID, -1, 0);
+
+	Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
+
+	BlessTupleDesc(tupdesc);
+
+	dbentry = pgstat_fetch_stat_dbentry(dbid);
+
+	if (dbentry == NULL)
+	{
+		/* If the subscription is not found, initialise its stats */
+		memset(&allzero, 0, sizeof(ExtVacReport));
+		extvacuum = &allzero;
+	}
+	else
+	{
+		extvacuum = &(dbentry->vacuum_ext);
+	}
+
+	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->wal_records);
+	values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+	/* Convert to numeric, like pg_stat_statements */
+	snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->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++] = Int32GetDatum(extvacuum->errors);
+
+	Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
+
 	/* Returns the record as Datum */
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
\ No newline at end of file
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 42f4cac5e0e..a24dec63f3a 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1514,7 +1514,7 @@ struct config_bool ConfigureNamesBool[] =
 			NULL
 		},
 		&pgstat_track_vacuum_statistics,
-		true,
+		false,
 		NULL, NULL, NULL
 	},
 	{
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 558b313d0db..4710bf997c4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12585,12 +12585,21 @@
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_rev_all_frozen_pages' },
 { oid => '8004',
-  descr => 'pg_stat_get_vacuum_indexes return stats values',
+  descr => 'pg_stat_get_vacuum_indexes returns vacuum stats values for index',
   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,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8}',
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{reloid,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}',
-  prosrc => 'pg_stat_get_vacuum_indexes' }
+  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,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,int4,int4}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{dbid,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}',
+  prosrc => 'pg_stat_get_vacuum_database' },
 ]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 4def2c60d1d..f8158aa353c 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -154,6 +154,9 @@ typedef struct ExtVacReport
 
 	int64		tuples_deleted;		/* tuples deleted by vacuum */
 
+	int32		errors;
+	int32		wraparound_failsafe_count;	/* the number of times to prevent wraparound problem */
+
 	ExtVacReportType type;		/* heap, index, etc. */
 
 	/* ----------
@@ -183,7 +186,6 @@ typedef struct ExtVacReport
 			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 */
-			int32		wraparound_failsafe_count;	/* number of emergency vacuums to prevent anti-wraparound shutdown */
 		}			table;
 		struct
 		{
@@ -762,6 +764,7 @@ extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter, TimestampTz starttime);
+extern void pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type);
 
 /*
  * If stats are enabled, but pending data hasn't been prepared yet, call
diff --git a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
index 5893d89573d..cfec3159580 100644
--- a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -18,6 +18,9 @@ teardown
 }
 
 session s1
+setup		{
+    SET track_vacuum_statistics TO 'on';
+    }
 step s1_begin_repeatable_read   {
   BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
   select count(ival) from test_vacuum_stat_isolation where id>900;
@@ -25,6 +28,9 @@ step s1_begin_repeatable_read   {
 step s1_commit                  { COMMIT; }
 
 session s2
+setup		{
+    SET track_vacuum_statistics TO 'on';
+    }
 step s2_insert                  { INSERT INTO test_vacuum_stat_isolation(id, ival) SELECT ival, ival%10 FROM generate_series(1,1000) As ival; }
 step s2_update                  { UPDATE test_vacuum_stat_isolation SET ival = ival + 2 where id > 900; }
 step s2_delete                  { DELETE FROM test_vacuum_stat_isolation where id > 900; }
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 4e5e5ca54da..f63f25f94d8 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2283,6 +2283,23 @@ 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,
diff --git a/src/test/regress/expected/vacuum_index_statistics.out b/src/test/regress/expected/vacuum_index_statistics.out
index e00a0fc683c..9e5d33342c9 100644
--- a/src/test/regress/expected/vacuum_index_statistics.out
+++ b/src/test/regress/expected/vacuum_index_statistics.out
@@ -16,8 +16,12 @@ SHOW track_counts;  -- must be on
 \set sample_size 10000
 -- not enabled by default, but we want to test it...
 SET track_functions TO 'all';
--- Test that vacuum statistics will be empty when parameter is off.
-SET track_vacuum_statistics TO 'off';
+SHOW track_vacuum_statistics;  -- must be off
+ track_vacuum_statistics 
+-------------------------
+ off
+(1 row)
+
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
 ANALYZE vestat;
@@ -33,12 +37,7 @@ WHERE vt.relname = 'vestat';
 
 RESET track_vacuum_statistics;
 DROP TABLE vestat CASCADE;
-SHOW track_vacuum_statistics;  -- must be on
- track_vacuum_statistics 
--------------------------
- on
-(1 row)
-
+SET track_vacuum_statistics TO 'on';
 -- ensure pending stats are flushed
 SELECT pg_stat_force_next_flush();
  pg_stat_force_next_flush 
@@ -181,3 +180,4 @@ WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
 (1 row)
 
 DROP TABLE vestat;
+RESET track_vacuum_statistics;
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
similarity index 82%
rename from src/test/regress/expected/vacuum_tables_statistics.out
rename to src/test/regress/expected/vacuum_tables_and_db_statistics.out
index b5ea9c9ab1e..0300e7b6276 100644
--- a/src/test/regress/expected/vacuum_tables_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -6,7 +6,6 @@
 -- number of frozen and visible pages removed by backend.
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
--- conditio sine qua non
 SHOW track_counts;  -- must be on
  track_counts 
 --------------
@@ -16,8 +15,12 @@ SHOW track_counts;  -- must be on
 \set sample_size 10000
 -- not enabled by default, but we want to test it...
 SET track_functions TO 'all';
--- Test that vacuum statistics will be empty when parameter is off.
-SET track_vacuum_statistics TO 'off';
+SHOW track_vacuum_statistics;  -- must be off
+ track_vacuum_statistics 
+-------------------------
+ off
+(1 row)
+
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
 ANALYZE vestat;
@@ -37,12 +40,12 @@ WHERE vt.relname = 'vestat';
 
 RESET track_vacuum_statistics;
 DROP TABLE vestat CASCADE;
-SHOW track_vacuum_statistics;  -- must be on
- track_vacuum_statistics 
--------------------------
- on
-(1 row)
-
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
+SET track_vacuum_statistics TO on;
+-- not enabled by default, but we want to test it...
+SET track_functions TO 'all';
 -- ensure pending stats are flushed
 SELECT pg_stat_force_next_flush();
  pg_stat_force_next_flush 
@@ -225,3 +228,69 @@ FROM pg_stat_vacuum_tables, pg_stat_all_tables WHERE pg_stat_vacuum_tables.relna
 (1 row)
 
 DROP TABLE vestat CASCADE;
+-- Now check vacuum statistics for current database
+SELECT dbname,
+       db_blks_hit > 0 AS db_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,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = current_database();
+             dbname             | db_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes | total_time 
+--------------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+------------
+ regression_statistic_vacuum_db | t           | t                  | t                  | t           | t       | t         | t
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+UPDATE vestat SET x = 10001;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+\c regression_statistic_vacuum_db1;
+SET track_vacuum_statistics TO on;
+-- Now check vacuum statistics for postgres database from another database
+SELECT dbname,
+       db_blks_hit > 0 AS db_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,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = 'regression_statistic_vacuum_db';
+             dbname             | db_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes | total_time 
+--------------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+------------
+ regression_statistic_vacuum_db | t           | t                  | t                  | t           | t       | t         | t
+(1 row)
+
+\c regression_statistic_vacuum_db
+SET track_vacuum_statistics TO on;
+DROP TABLE vestat CASCADE;
+\c regression_statistic_vacuum_db1;
+SET track_vacuum_statistics TO on;
+SELECT count(*)
+FROM pg_database d
+CROSS JOIN pg_stat_get_vacuum_tables(0)
+WHERE oid = 0; -- must be 0
+ count 
+-------
+     0
+(1 row)
+
+\c postgres
+DROP DATABASE regression_statistic_vacuum_db1;
+DROP DATABASE regression_statistic_vacuum_db;
+RESET track_vacuum_statistics;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 0197830b5cd..fa2489716cc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -145,4 +145,4 @@ test: tablespace
 # Check vacuum statistics
 # ----------
 test: vacuum_index_statistics
-test: vacuum_tables_statistics
\ No newline at end of file
+test: vacuum_tables_and_db_statistics
\ No newline at end of file
diff --git a/src/test/regress/sql/vacuum_index_statistics.sql b/src/test/regress/sql/vacuum_index_statistics.sql
index ae146e1d23f..9b7e645187d 100644
--- a/src/test/regress/sql/vacuum_index_statistics.sql
+++ b/src/test/regress/sql/vacuum_index_statistics.sql
@@ -14,8 +14,7 @@ SHOW track_counts;  -- must be on
 -- not enabled by default, but we want to test it...
 SET track_functions TO 'all';
 
--- Test that vacuum statistics will be empty when parameter is off.
-SET track_vacuum_statistics TO 'off';
+SHOW track_vacuum_statistics;  -- must be off
 
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
@@ -33,7 +32,7 @@ WHERE vt.relname = 'vestat';
 RESET track_vacuum_statistics;
 DROP TABLE vestat CASCADE;
 
-SHOW track_vacuum_statistics;  -- must be on
+SET track_vacuum_statistics TO 'on';
 
 -- ensure pending stats are flushed
 SELECT pg_stat_force_next_flush();
@@ -149,3 +148,4 @@ FROM pg_stat_vacuum_indexes vt, pg_class c
 WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
 
 DROP TABLE vestat;
+RESET track_vacuum_statistics;
diff --git a/src/test/regress/sql/vacuum_tables_statistics.sql b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
similarity index 81%
rename from src/test/regress/sql/vacuum_tables_statistics.sql
rename to src/test/regress/sql/vacuum_tables_and_db_statistics.sql
index 5bc34bec64b..ca7dbde9387 100644
--- a/src/test/regress/sql/vacuum_tables_statistics.sql
+++ b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
@@ -7,15 +7,13 @@
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
 
--- conditio sine qua non
 SHOW track_counts;  -- must be on
 \set sample_size 10000
 
 -- not enabled by default, but we want to test it...
 SET track_functions TO 'all';
 
--- Test that vacuum statistics will be empty when parameter is off.
-SET track_vacuum_statistics TO 'off';
+SHOW track_vacuum_statistics;  -- must be off
 
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
@@ -36,7 +34,13 @@ WHERE vt.relname = 'vestat';
 RESET track_vacuum_statistics;
 DROP TABLE vestat CASCADE;
 
-SHOW track_vacuum_statistics;  -- must be on
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
+SET track_vacuum_statistics TO on;
+
+-- not enabled by default, but we want to test it...
+SET track_functions TO 'all';
 
 -- ensure pending stats are flushed
 SELECT pg_stat_force_next_flush();
@@ -180,4 +184,59 @@ VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 SELECT vm_new_frozen_pages = :pf AS vm_new_frozen_pages,vm_new_visible_pages = :pv AS vm_new_visible_pages,vm_new_visible_frozen_pages = :pvf AS vm_new_visible_frozen_pages, rev_all_frozen_pages = :hafp AS rev_all_frozen_pages,rev_all_visible_pages = :havp AS rev_all_visible_pages
 FROM pg_stat_vacuum_tables, pg_stat_all_tables WHERE pg_stat_vacuum_tables.relname = 'vestat' and pg_stat_vacuum_tables.relid = pg_stat_all_tables.relid;
 
-DROP TABLE vestat CASCADE;
\ No newline at end of file
+DROP TABLE vestat CASCADE;
+
+-- Now check vacuum statistics for current database
+SELECT dbname,
+       db_blks_hit > 0 AS db_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,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = current_database();
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+UPDATE vestat SET x = 10001;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+
+\c regression_statistic_vacuum_db1;
+SET track_vacuum_statistics TO on;
+
+-- Now check vacuum statistics for postgres database from another database
+SELECT dbname,
+       db_blks_hit > 0 AS db_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,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = 'regression_statistic_vacuum_db';
+
+\c regression_statistic_vacuum_db
+SET track_vacuum_statistics TO on;
+
+DROP TABLE vestat CASCADE;
+
+\c regression_statistic_vacuum_db1;
+SET track_vacuum_statistics TO on;
+SELECT count(*)
+FROM pg_database d
+CROSS JOIN pg_stat_get_vacuum_tables(0)
+WHERE oid = 0; -- must be 0
+
+\c postgres
+DROP DATABASE regression_statistic_vacuum_db1;
+DROP DATABASE regression_statistic_vacuum_db;
+RESET track_vacuum_statistics;
\ No newline at end of file
-- 
2.34.1

