Subject: Re: Vacuum statistics

From: Alena Rybakina <[email-masked]>
Date: Thu, 15 Aug 2024 11:49:36 +0300

Hi!

On 13.08.2024 16:18, Ilia Evdokimov wrote:
>
> On 10.8.24 22:37, Andrei Zubkov wrote:
>
>> Hi, Ilia!
>>
>>> Do you consider not to create new table in pg_catalog but to save
>>> statistics in existing table? I mean pg_class or
>>> pg_stat_progress_analyze, pg_stat_progress_vacuum?
>>>
>> Thank you for your interest on our patch!
>>
>> *_progress views is not our case. They hold online statistics while
>> vacuum is in progress. Once work is done on a table the entry is gone
>> from those views. Idea of this patch is the opposite - it doesn't
>> provide online statistics but it accumulates statistics about rosources
>> consumed by all vacuum passes over all relations. It's much closer to
>> the pg_stat_all_tables than pg_stat_progress_vacuum.
>>
>> It seems pg_class is not the right place because it is not a statistic
>> view - it holds the current relation state and haven't anything about
>> the relation workload.
>>
>> Maybe the pg_stat_all_tables is the right place but I have several
>> thoughts about why it is not:
>> - Some statistics provided by this patch is really vacuum specific. I
>> don't think we want them in the relation statistics view.
>> - Postgres is extreamly extensible. I'm sure someday there will be
>> table AMs that does not need the vacuum at all.
>>
>> Right now vacuum specific workload views seems optimal choice to me.
>>
>> Regards,
>
>
> Agreed. They are not god places to store such statistics.
>
>
> I have some suggestions:
>
>  1. pgstatfuncs.c in functions tuplestore_put_for_database() and
>     tuplestore_put_for_relation you can remove 'nulls' array if you're
>     sure that columns cannot be NULL.
>
We need to use this for tuplestore_putvalues function. With this 
function, we fill the table with the values of the statistics.
>
> 1.
>
>
>  2. These functions are almost the same and I would think of writing
>     one function depending of type 'ExtVacReportType'
>
I'm not sure that I fully understand what you mean. Can you explain it 
more clearly, please?

On 13.08.2024 16:37, Ilia Evdokimov wrote:
> And I have one suggestion for pg_stat_vacuum_database: I suppose we 
> should add database's name column after 'dboid' column because it is 
> difficult to read statistics without database's name. We could call it 
> 'datname' just like in 'pg_stat_database' view.
>
Thank you. Fixed.

-- 
Regards,
Alena Rybakina
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
From ce8377fb8166da3636a73201d60dec06f46655b1 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Thu, 15 Aug 2024 11:30:13 +0300
Subject: [PATCH 1/4] Machinery for grabbing an extended vacuum statistics on 
 heap relations.

Value of total_blks_hit, total_blks_read, total_blks_dirtied are number of
hitted, missed and dirtied pages in shared buffers during a vacuum operation
respectively.

total_blks_dirtied means 'dirtied only by this action'. So, if this page was
dirty before the vacuum operation, it doesn't count this page as 'dirtied'.

The tuples_deleted parameter is the number of tuples cleaned up by the vacuum
operation.

The delay_time value means total vacuum sleep time in vacuum delay point.
The pages_removed value is the number of pages by which the physical data
storage of the relation was reduced.
The value of pages_deleted parameter is the number of freed pages in the table
(file size may not have changed).

Interruptions number of (auto)vacuum process during vacuuming of a relation.
We report from the vacuum_error_callback routine. So we can log all ERROR
reports. In the case of autovacuum we can report SIGINT signals too.
It maybe dangerous to make such complex task (send) in an error callback -
we can catch ERROR in ERROR problem. But it looks like we have so small
chance to stuck into this problem. So, let's try to use.
This parameter relates to a problem, covered by b19e4250.

Tracking of IO during an (auto)vacuum operation.
Introduced variables blk_read_time and blk_write_time tracks only access to
buffer pages and flushing them to disk. Reading operation is trivial, but
writing measurement technique is not obvious.
So, during a vacuum writing time can be zero incremented because no any flushing
operations were performed.

System time and user time are parameters that describes how much time a vacuum
operation has spent in executing of code in user space and kernel space
accordingly. Also, accumulate total time of a vacuum that is a diff between
timestamps in start and finish points in the vacuum code.
Remember about idle time, when vacuum waited for IO and locks, so total time
isn't equal a sum of user and system time, but no less.

pages_frozen - number of pages that are marked as frozen in vm during vacuum.
This parameter is incremented if page is marked as all-frozen.
pages_all_visible - number of pages that are marked as all-visible in vm during
vacuum.

Authors: Alena Rybakina <[email-masked]>,
	 Andrei Lepikhov <[email-masked]>,
	 Andrei Zubkov <[email-masked]>
---
 src/backend/access/heap/vacuumlazy.c          | 159 +++++++++++++-
 src/backend/access/heap/visibilitymap.c       |  13 ++
 src/backend/catalog/system_views.sql          |  54 +++++
 src/backend/commands/vacuum.c                 |   4 +
 src/backend/commands/vacuumparallel.c         |   1 +
 src/backend/utils/activity/pgstat.c           |  92 +++++---
 src/backend/utils/activity/pgstat_relation.c  |  35 ++-
 src/backend/utils/adt/pgstatfuncs.c           | 186 ++++++++++++++++
 src/backend/utils/error/elog.c                |  13 ++
 src/include/catalog/pg_proc.dat               |  10 +-
 src/include/commands/vacuum.h                 |   1 +
 src/include/pgstat.h                          |  84 +++++++-
 src/include/utils/elog.h                      |   2 +-
 src/include/utils/pgstat_internal.h           |  36 +++-
 .../vacuum-extending-in-repetable-read.out    |  53 +++++
 src/test/isolation/isolation_schedule         |   1 +
 .../vacuum-extending-in-repetable-read.spec   |  51 +++++
 src/test/regress/expected/opr_sanity.out      |   7 +-
 src/test/regress/expected/rules.out           |  34 +++
 .../expected/vacuum_tables_statistics.out     | 200 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 .../regress/sql/vacuum_tables_statistics.sql  | 158 ++++++++++++++
 22 files changed, 1155 insertions(+), 44 deletions(-)
 create mode 100644 src/test/isolation/expected/vacuum-extending-in-repetable-read.out
 create mode 100644 src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
 create mode 100644 src/test/regress/expected/vacuum_tables_statistics.out
 create mode 100644 src/test/regress/sql/vacuum_tables_statistics.sql

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index d82aa3d4896..3941ae26f2d 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -167,6 +167,7 @@ typedef struct LVRelState
 	/* Error reporting state */
 	char	   *dbname;
 	char	   *relnamespace;
+	Oid			reloid;
 	char	   *relname;
 	char	   *indname;		/* Current index name */
 	BlockNumber blkno;			/* used only for heap operations */
@@ -194,6 +195,8 @@ typedef struct LVRelState
 	BlockNumber lpdead_item_pages;	/* # pages with LP_DEAD items */
 	BlockNumber missed_dead_pages;	/* # pages with missed dead tuples */
 	BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
+	BlockNumber set_frozen_pages; /* pages are marked as frozen in vm during vacuum */
+	BlockNumber set_all_visible_pages;	/* pages are marked as all-visible in vm during vacuum */
 
 	/* Statistics output by us, for table */
 	double		new_rel_tuples; /* new estimated total # of tuples */
@@ -226,6 +229,22 @@ typedef struct LVSavedErrInfo
 	VacErrPhase phase;
 } LVSavedErrInfo;
 
+/*
+ * Cut-off values of parameters which changes implicitly during a vacuum
+ * process.
+ * Vacuum can't control their values, so we should store them before and after
+ * the processing.
+ */
+typedef struct LVExtStatCounters
+{
+	TimestampTz time;
+	PGRUsage	ru;
+	WalUsage	walusage;
+	BufferUsage bufusage;
+	double		VacuumDelayTime;
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
+} LVExtStatCounters;
 
 /* non-export function prototypes */
 static void lazy_scan_heap(LVRelState *vacrel);
@@ -279,6 +298,115 @@ static void update_vacuum_error_info(LVRelState *vacrel,
 static void restore_vacuum_error_info(LVRelState *vacrel,
 									  const LVSavedErrInfo *saved_vacrel);
 
+/* ----------
+ * extvac_stats_start() -
+ *
+ * Save cut-off values of extended vacuum counters before start of a relation
+ * processing.
+ * ----------
+ */
+static void
+extvac_stats_start(Relation rel, LVExtStatCounters *counters)
+{
+	TimestampTz	starttime;
+	PGRUsage	ru0;
+
+	memset(counters, 0, sizeof(LVExtStatCounters));
+
+	pg_rusage_init(&ru0);
+	starttime = GetCurrentTimestamp();
+
+	counters->ru = ru0;
+	counters->time = starttime;
+	counters->walusage = pgWalUsage;
+	counters->bufusage = pgBufferUsage;
+	counters->VacuumDelayTime = VacuumDelayTime;
+	counters->blocks_fetched = 0;
+	counters->blocks_hit = 0;
+
+	if (!rel->pgstat_info || !pgstat_track_counts)
+		/*
+		 * if something goes wrong or an user doesn't want to track a database
+		 * activity - just suppress it.
+		 */
+		return;
+
+	counters->blocks_fetched = rel->pgstat_info->counts.blocks_fetched;
+	counters->blocks_hit = rel->pgstat_info->counts.blocks_hit;
+}
+
+/* ----------
+ * extvac_stats_end() -
+ *
+ *	Called to finish an extended vacuum statistic gathering and form a report.
+ * ----------
+ */
+static void
+extvac_stats_end(Relation rel, LVExtStatCounters *counters,
+				  ExtVacReport *report)
+{
+	WalUsage	walusage;
+	BufferUsage	bufusage;
+	TimestampTz endtime;
+	long		secs;
+	int			usecs;
+	PGRUsage	ru1;
+
+	/* Calculate diffs of global stat parameters on WAL and buffer usage. */
+	memset(&walusage, 0, sizeof(WalUsage));
+	WalUsageAccumDiff(&walusage, &pgWalUsage, &counters->walusage);
+
+	memset(&bufusage, 0, sizeof(BufferUsage));
+	BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &counters->bufusage);
+
+	endtime = GetCurrentTimestamp();
+	TimestampDifference(counters->time, endtime, &secs, &usecs);
+
+	memset(report, 0, sizeof(ExtVacReport));
+
+	/*
+	 * Fill additional statistics on a vacuum processing operation.
+	 */
+	report->total_blks_read = bufusage.local_blks_read + bufusage.shared_blks_read;
+	report->total_blks_hit = bufusage.local_blks_hit + bufusage.shared_blks_hit;
+	report->total_blks_dirtied = bufusage.local_blks_dirtied + bufusage.shared_blks_dirtied;
+	report->total_blks_written = bufusage.shared_blks_written;
+
+	report->wal_records = walusage.wal_records;
+	report->wal_fpi = walusage.wal_fpi;
+	report->wal_bytes = walusage.wal_bytes;
+
+	report->blk_read_time = INSTR_TIME_GET_MILLISEC(bufusage.local_blk_read_time);
+	report->blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage.shared_blk_read_time);
+	report->blk_write_time = INSTR_TIME_GET_MILLISEC(bufusage.local_blk_write_time);
+	report->blk_write_time = INSTR_TIME_GET_MILLISEC(bufusage.shared_blk_write_time);
+	report->delay_time = VacuumDelayTime - counters->VacuumDelayTime;
+
+	/*
+	 * Get difference of a system time and user time values in milliseconds.
+	 * Use floating point representation to show tails of time diffs.
+	 */
+	pg_rusage_init(&ru1);
+	report->system_time =
+		(ru1.ru.ru_stime.tv_sec - counters->ru.ru.ru_stime.tv_sec) * 1000. +
+		(ru1.ru.ru_stime.tv_usec - counters->ru.ru.ru_stime.tv_usec) * 0.001;
+	report->user_time =
+		(ru1.ru.ru_utime.tv_sec - counters->ru.ru.ru_utime.tv_sec) * 1000. +
+		(ru1.ru.ru_utime.tv_usec - counters->ru.ru.ru_utime.tv_usec) * 0.001;
+	report->total_time = secs * 1000. + usecs / 1000.;
+
+	if (!rel->pgstat_info || !pgstat_track_counts)
+		/*
+		 * if something goes wrong or an user doesn't want to track a database
+		 * activity - just suppress it.
+		 */
+		return;
+
+	report->blks_fetched =
+		rel->pgstat_info->counts.blocks_fetched - counters->blocks_fetched;
+	report->blks_hit =
+		rel->pgstat_info->counts.blocks_hit - counters->blocks_hit;
+}
 
 /*
  *	heap_vacuum_rel() -- perform VACUUM for one heap relation
@@ -311,6 +439,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	ErrorContextCallback errcallback;
+	LVExtStatCounters extVacCounters;
+	ExtVacReport extVacReport;
 	char	  **indnames = NULL;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
@@ -329,7 +459,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 
 	pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
 								  RelationGetRelid(rel));
-
+	extvac_stats_start(rel, &extVacCounters);
 	/*
 	 * Setup error traceback support for ereport() first.  The idea is to set
 	 * up an error context callback to display additional information on any
@@ -346,6 +476,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	vacrel->dbname = get_database_name(MyDatabaseId);
 	vacrel->relnamespace = get_namespace_name(RelationGetNamespace(rel));
 	vacrel->relname = pstrdup(RelationGetRelationName(rel));
+	vacrel->reloid = RelationGetRelid(rel);
 	vacrel->indname = NULL;
 	vacrel->phase = VACUUM_ERRCB_PHASE_UNKNOWN;
 	vacrel->verbose = verbose;
@@ -413,6 +544,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	vacrel->lpdead_item_pages = 0;
 	vacrel->missed_dead_pages = 0;
 	vacrel->nonempty_pages = 0;
+	vacrel->set_frozen_pages = 0;
+	vacrel->set_all_visible_pages = 0;
 	/* dead_items_alloc allocates vacrel->dead_items later on */
 
 	/* Allocate/initialize output statistics state */
@@ -574,6 +707,19 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 						vacrel->NewRelfrozenXid, vacrel->NewRelminMxid,
 						&frozenxid_updated, &minmulti_updated, false);
 
+	/* Make generic extended vacuum stats report */
+	extvac_stats_end(rel, &extVacCounters, &extVacReport);
+
+	/* Fill heap-specific extended stats fields */
+	extVacReport.pages_scanned = vacrel->scanned_pages;
+	extVacReport.pages_removed = vacrel->removed_pages;
+	extVacReport.pages_frozen = vacrel->set_frozen_pages;
+	extVacReport.pages_all_visible = vacrel->set_all_visible_pages;
+	extVacReport.tuples_deleted = vacrel->tuples_deleted;
+	extVacReport.tuples_frozen = vacrel->tuples_frozen;
+	extVacReport.dead_tuples = vacrel->recently_dead_tuples + vacrel->missed_dead_tuples;
+	extVacReport.index_vacuum_count = vacrel->num_index_scans;
+
 	/*
 	 * Report results to the cumulative stats system, too.
 	 *
@@ -588,7 +734,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 						 rel->rd_rel->relisshared,
 						 Max(vacrel->new_live_tuples, 0),
 						 vacrel->recently_dead_tuples +
-						 vacrel->missed_dead_tuples);
+						 vacrel->missed_dead_tuples,
+						 &extVacReport);
 	pgstat_progress_end_command();
 
 	if (instrument)
@@ -1380,6 +1527,8 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
 							  vmbuffer, InvalidTransactionId,
 							  VISIBILITYMAP_ALL_VISIBLE | VISIBILITYMAP_ALL_FROZEN);
 			END_CRIT_SECTION();
+			vacrel->set_all_visible_pages++;
+			vacrel->set_frozen_pages++;
 		}
 
 		freespace = PageGetHeapFreeSpace(page);
@@ -2277,11 +2426,13 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
 								 &all_frozen))
 	{
 		uint8		flags = VISIBILITYMAP_ALL_VISIBLE;
+		vacrel->set_all_visible_pages++;
 
 		if (all_frozen)
 		{
 			Assert(!TransactionIdIsValid(visibility_cutoff_xid));
 			flags |= VISIBILITYMAP_ALL_FROZEN;
+			vacrel->set_frozen_pages++;
 		}
 
 		PageSetAllVisible(page);
@@ -3122,6 +3273,8 @@ vacuum_error_callback(void *arg)
 	switch (errinfo->phase)
 	{
 		case VACUUM_ERRCB_PHASE_SCAN_HEAP:
+			if(geterrelevel() >= ERROR)
+				pgstat_report_vacuum_error(errinfo->reloid);
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
 				if (OffsetNumberIsValid(errinfo->offnum))
@@ -3137,6 +3290,8 @@ vacuum_error_callback(void *arg)
 			break;
 
 		case VACUUM_ERRCB_PHASE_VACUUM_HEAP:
+			if(geterrelevel() >= ERROR)
+				pgstat_report_vacuum_error(errinfo->reloid);
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
 				if (OffsetNumberIsValid(errinfo->offnum))
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 8b24e7bc33c..d72cade60a4 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,6 +91,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
@@ -160,6 +161,18 @@ visibilitymap_clear(Relation rel, BlockNumber heapBlk, Buffer vmbuf, uint8 flags
 
 	if (map[mapByte] & mask)
 	{
+		/*
+		 * Initially, it didn't matter what type of flags (all-visible or frozen) we received,
+		 * we just performed a reverse concatenation operation. But this information is very important
+		 * for vacuum statistics. We need to find out this usingthe bit concatenation operation
+		 * with the VISIBILITYMAP_ALL_VISIBLE and VISIBILITYMAP_ALL_FROZEN masks,
+		 * and where the desired one matches, we increment the value there.
+		*/
+		if (map[mapByte] >> mapOffset & flags & VISIBILITYMAP_ALL_VISIBLE)
+			pgstat_count_vm_rev_all_visible(rel);
+		if (map[mapByte] >> mapOffset & flags & VISIBILITYMAP_ALL_FROZEN)
+			pgstat_count_vm_rev_all_frozen(rel);
+
 		map[mapByte] &= ~mask;
 
 		MarkBufferDirty(vmbuf);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 19cabc9a47f..68e6bfe6115 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1371,3 +1371,57 @@ CREATE VIEW pg_stat_subscription_stats AS
 
 CREATE VIEW pg_wait_events AS
     SELECT * FROM pg_get_wait_events();
+--
+-- Show extended cumulative statistics on a vacuum operation over all tables and
+-- databases of the instance.
+-- Use Invalid Oid "0" as an input relation id to get stat on each table in a
+-- database.
+--
+
+CREATE VIEW pg_stat_vacuum_tables AS
+SELECT
+  rel.oid as relid,
+  ns.nspname AS "schema",
+  rel.relname AS 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_scanned,
+  stats.pages_removed,
+  stats.pages_frozen,
+  stats.pages_all_visible,
+  stats.tuples_deleted,
+  stats.tuples_frozen,
+  stats.dead_tuples,
+
+  stats.index_vacuum_count,
+  stats.rev_all_frozen_pages,
+  stats.rev_all_visible_pages,
+
+  stats.wal_records,
+  stats.wal_fpi,
+  stats.wal_bytes,
+
+  stats.blk_read_time,
+  stats.blk_write_time,
+
+  stats.delay_time,
+  stats.system_time,
+  stats.user_time,
+  stats.total_time,
+  stats.interrupts
+FROM
+  pg_database db,
+  pg_class rel,
+  pg_namespace ns,
+  pg_stat_vacuum_tables(db.oid, rel.oid) stats
+WHERE
+  db.datname = current_database() AND
+  rel.oid = stats.relid AND
+  ns.oid = rel.relnamespace;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7d8e9d20454..363924d00db 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -103,6 +103,9 @@ pg_atomic_uint32 *VacuumSharedCostBalance = NULL;
 pg_atomic_uint32 *VacuumActiveNWorkers = NULL;
 int			VacuumCostBalanceLocal = 0;
 
+/* Cumulative storage to report total vacuum delay time. */
+double VacuumDelayTime = 0; /* msec. */
+
 /* non-export function prototypes */
 static List *expand_vacuum_rel(VacuumRelation *vrel,
 							   MemoryContext vac_context, int options);
@@ -2394,6 +2397,7 @@ vacuum_delay_point(void)
 			exit(1);
 
 		VacuumCostBalance = 0;
+		VacuumDelayTime += msec;
 
 		/*
 		 * Balance and update limit values for autovacuum workers. We must do
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 22c057fe61b..13ab633086a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1043,6 +1043,7 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
 	/* Set cost-based vacuum delay */
 	VacuumUpdateCosts();
 	VacuumCostBalance = 0;
+	VacuumDelayTime = 0;
 	VacuumCostBalanceLocal = 0;
 	VacuumSharedCostBalance = &(shared->cost_balance);
 	VacuumActiveNWorkers = &(shared->active_nworkers);
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index b2ca3f39b7a..6a788f2b586 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -146,34 +146,6 @@
 #define PGSTAT_FILE_ENTRY_HASH	'S' /* stats entry identified by
 									 * PgStat_HashKey */
 
-/* hash table for statistics snapshots entry */
-typedef struct PgStat_SnapshotEntry
-{
-	PgStat_HashKey key;
-	char		status;			/* for simplehash use */
-	void	   *data;			/* the stats data itself */
-} PgStat_SnapshotEntry;
-
-
-/* ----------
- * Backend-local Hash Table Definitions
- * ----------
- */
-
-/* for stats snapshot entries */
-#define SH_PREFIX pgstat_snapshot
-#define SH_ELEMENT_TYPE PgStat_SnapshotEntry
-#define SH_KEY_TYPE PgStat_HashKey
-#define SH_KEY key
-#define SH_HASH_KEY(tb, key) \
-	pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
-#define SH_EQUAL(tb, a, b) \
-	pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0
-#define SH_SCOPE static inline
-#define SH_DEFINE
-#define SH_DECLARE
-#include "lib/simplehash.h"
-
 
 /* ----------
  * Local function forward declarations
@@ -190,7 +162,7 @@ static void pgstat_reset_after_failure(void);
 static bool pgstat_flush_pending_entries(bool nowait);
 
 static void pgstat_prep_snapshot(void);
-static void pgstat_build_snapshot(void);
+static void pgstat_build_snapshot(PgStat_Kind statKind);
 static void pgstat_build_snapshot_fixed(PgStat_Kind kind);
 
 static inline bool pgstat_is_kind_valid(PgStat_Kind kind);
@@ -830,6 +802,40 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 		pgstat_reset_entries_of_kind(kind, ts);
 }
 
+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->system_time += src->system_time;
+	dst->user_time += src->user_time;
+	dst->total_time += src->total_time;
+	dst->interrupts += src->interrupts;
+
+	if (!accumulate_reltype_specific_info)
+		return;
+
+	dst->blks_fetched += src->blks_fetched;
+	dst->blks_hit += src->blks_hit;
+
+	dst->pages_scanned += src->pages_scanned;
+	dst->pages_removed += src->pages_removed;
+	dst->pages_frozen += src->pages_frozen;
+	dst->pages_all_visible += src->pages_all_visible;
+	dst->tuples_deleted += src->tuples_deleted;
+	dst->tuples_frozen += src->tuples_frozen;
+	dst->dead_tuples += src->dead_tuples;
+	dst->index_vacuum_count += src->index_vacuum_count;
+}
 
 /* ------------------------------------------------------------
  * Fetching of stats
@@ -896,7 +902,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
 
 	/* if we need to build a full snapshot, do so */
 	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
-		pgstat_build_snapshot();
+		pgstat_build_snapshot(PGSTAT_KIND_INVALID);
 
 	/* if caching is desired, look up in cache */
 	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
@@ -1012,7 +1018,7 @@ pgstat_snapshot_fixed(PgStat_Kind kind)
 		pgstat_clear_snapshot();
 
 	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
-		pgstat_build_snapshot();
+		pgstat_build_snapshot(PGSTAT_KIND_INVALID);
 	else
 		pgstat_build_snapshot_fixed(kind);
 
@@ -1062,8 +1068,30 @@ pgstat_prep_snapshot(void)
 							   NULL);
 }
 
+
+/*
+ * Trivial external interface to build a snapshot for table statistics only.
+ */
+void
+pgstat_update_snapshot(PgStat_Kind kind)
+{
+	int save_consistency_guc = pgstat_fetch_consistency;
+	pgstat_clear_snapshot();
+
+	PG_TRY();
+	{
+		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
+		pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+	}
+	PG_FINALLY();
+	{
+		pgstat_fetch_consistency = save_consistency_guc;
+	}
+	PG_END_TRY();
+}
+
 static void
-pgstat_build_snapshot(void)
+pgstat_build_snapshot(PgStat_Kind statKind)
 {
 	dshash_seq_status hstat;
 	PgStatShared_HashEntry *p;
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 8a3f7d434cf..d40d43cdb4a 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -204,12 +204,40 @@ pgstat_drop_relation(Relation rel)
 	}
 }
 
+/* ---------
+ * pgstat_report_vacuum_error() -
+ *
+ *	Tell the collector about an (auto)vacuum interruption.
+ * ---------
+ */
+void
+pgstat_report_vacuum_error(Oid tableoid)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_Relation *shtabentry;
+	PgStat_StatTabEntry *tabentry;
+	Oid			dboid =  MyDatabaseId;
+
+	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.interrupts++;
+	pgstat_unlock_entry(entry_ref);
+}
+
 /*
  * Report that the table was just vacuumed and flush IO statistics.
  */
 void
 pgstat_report_vacuum(Oid tableoid, bool shared,
-					 PgStat_Counter livetuples, PgStat_Counter deadtuples)
+					 PgStat_Counter livetuples, PgStat_Counter deadtuples,
+					 ExtVacReport *params)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Relation *shtabentry;
@@ -233,6 +261,8 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	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.
@@ -861,6 +891,9 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	tabentry->blocks_fetched += lstats->counts.blocks_fetched;
 	tabentry->blocks_hit += lstats->counts.blocks_hit;
 
+	tabentry->rev_all_frozen_pages += lstats->counts.rev_all_frozen_pages;
+	tabentry->rev_all_visible_pages += lstats->counts.rev_all_visible_pages;
+
 	/* Clamp live_tuples in case of negative delta_live_tuples */
 	tabentry->live_tuples = Max(tabentry->live_tuples, 0);
 	/* Likewise for dead_tuples */
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 32211371237..f94e562009d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -31,6 +31,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
+#include "utils/pgstat_internal.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -2032,3 +2033,188 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid));
 }
+
+#define EXTVACHEAPSTAT_COLUMNS	27
+
+static Oid CurrentDatabaseId = InvalidOid;
+
+
+/*
+ * Fetch stat collector data for specific database and table, which loading from disc.
+ * It is maybe expensive, but i guess we won't use that machinery often.
+ * The kind of bufferization is based on CurrentDatabaseId value.
+ */
+static PgStat_StatTabEntry *
+fetch_dbstat_tabentry(Oid dbid, Oid relid)
+{
+	Oid						storedMyDatabaseId = MyDatabaseId;
+	PgStat_StatTabEntry 	*tabentry = NULL;
+
+	if (OidIsValid(CurrentDatabaseId) && CurrentDatabaseId == dbid)
+		/* Quick path when we read data from the same database */
+		return pgstat_fetch_stat_tabentry(relid);
+
+	pgstat_clear_snapshot();
+
+	/* Tricky turn here: enforce pgstat to think that our database has dbid */
+
+	MyDatabaseId = dbid;
+
+	PG_TRY();
+	{
+		tabentry = pgstat_fetch_stat_tabentry(relid);
+		MyDatabaseId = storedMyDatabaseId;
+	}
+	PG_CATCH();
+	{
+		MyDatabaseId = storedMyDatabaseId;
+	}
+	PG_END_TRY();
+
+	return tabentry;
+}
+
+static void
+tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
+			   TupleDesc tupdesc, PgStat_StatTabEntry *tabentry, int ncolumns)
+{
+	Datum		values[EXTVACHEAPSTAT_COLUMNS];
+	bool		nulls[EXTVACHEAPSTAT_COLUMNS];
+	char		buf[256];
+	int			i = 0;
+
+	memset(nulls, 0, EXTVACHEAPSTAT_COLUMNS * sizeof(bool));
+
+	values[i++] = ObjectIdGetDatum(relid);
+
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_read);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_hit);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_dirtied);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_written);
+
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_fetched -
+									tabentry->vacuum_ext.blks_hit);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_hit);
+
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_scanned);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_removed);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_frozen);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_all_visible);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.tuples_deleted);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.tuples_frozen);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.dead_tuples);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.index_vacuum_count);
+	values[i++] = Int64GetDatum(tabentry->rev_all_frozen_pages);
+	values[i++] = Int64GetDatum(tabentry->rev_all_visible_pages);
+
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_records);
+	values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_fpi);
+
+	/* Convert to numeric, like pg_stat_statements */
+	snprintf(buf, sizeof buf, UINT64_FORMAT, tabentry->vacuum_ext.wal_bytes);
+	values[i++] = DirectFunctionCall3(numeric_in,
+									  CStringGetDatum(buf),
+									  ObjectIdGetDatum(0),
+									  Int32GetDatum(-1));
+
+	values[i++] = Float8GetDatum(tabentry->vacuum_ext.blk_read_time);
+	values[i++] = Float8GetDatum(tabentry->vacuum_ext.blk_write_time);
+	values[i++] = Float8GetDatum(tabentry->vacuum_ext.delay_time);
+	values[i++] = Float8GetDatum(tabentry->vacuum_ext.system_time);
+	values[i++] = Float8GetDatum(tabentry->vacuum_ext.user_time);
+	values[i++] = Float8GetDatum(tabentry->vacuum_ext.total_time);
+	values[i++] = Int32GetDatum(tabentry->vacuum_ext.interrupts);
+
+	Assert(i == ncolumns);
+
+	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+}
+
+/*
+ * Get the vacuum statistics for the heap tables or indexes.
+ */
+static Datum
+pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
+{
+	ReturnSetInfo		   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext			per_query_ctx;
+	MemoryContext			oldcontext;
+	Tuplestorestate		   *tupstore;
+	TupleDesc				tupdesc;
+	Oid						dbid = PG_GETARG_OID(0);
+	Oid						relid = PG_GETARG_OID(1);
+	PgStat_StatTabEntry    *tabentry;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* Check if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	/* Switch to long-lived context to create the returned data structures */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	/* 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");
+
+	Assert(tupdesc->natts == ncolumns);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	Assert (tupstore != NULL);
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Load table statistics for specified database. */
+	if (OidIsValid(relid))
+	{
+		tabentry = fetch_dbstat_tabentry(dbid, relid);
+		if (tabentry == NULL)
+			/* Table don't exists or isn't an heap relation. */
+			PG_RETURN_NULL();
+
+		tuplestore_put_for_relation(relid, tupstore, tupdesc, tabentry, ncolumns);
+	}
+	else
+	{
+		SnapshotIterator		hashiter;
+		PgStat_SnapshotEntry   *entry;
+		Oid						storedMyDatabaseId = MyDatabaseId;
+
+		pgstat_update_snapshot(PGSTAT_KIND_RELATION);
+		MyDatabaseId = storedMyDatabaseId;
+
+
+		/* Iterate the snapshot */
+		InitSnapshotIterator(pgStatLocal.snapshot.stats, &hashiter);
+
+		while ((entry = ScanStatSnapshot(pgStatLocal.snapshot.stats, &hashiter)) != NULL)
+		{
+			Oid	reloid;
+
+			CHECK_FOR_INTERRUPTS();
+
+			tabentry = (PgStat_StatTabEntry *) entry->data;
+			reloid = entry->key.objoid;
+
+			if (tabentry != NULL)
+				tuplestore_put_for_relation(reloid, tupstore, tupdesc, tabentry, ncolumns);
+		}
+	}
+	PG_RETURN_NULL();
+}
+
+/*
+ * Get the vacuum statistics for the heap tables.
+ */
+Datum
+pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
+{
+	return pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
+
+	PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 943d8588f3d..93db1232df9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -1602,6 +1602,19 @@ getinternalerrposition(void)
 	return edata->internalpos;
 }
 
+/*
+ * Return elevel of errors
+ */
+int
+geterrelevel(void)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+
+	/* we don't bother incrementing recursion_depth */
+	CHECK_STACK_DEPTH();
+
+	return edata->elevel;
+}
 
 /*
  * Functions to allow construction of error message strings separately from
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4abc6d95262..443c4ec65d3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12254,5 +12254,13 @@
   proallargtypes => '{int8,pg_lsn,pg_lsn,int4}', proargmodes => '{o,o,o,o}',
   proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}',
   prosrc => 'pg_get_wal_summarizer_state' },
-
+{ oid => '8001',
+  descr => 'pg_stat_vacuum_tables return stats values',
+  proname => 'pg_stat_vacuum_tables', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proretset => 't',
+  proargtypes => 'oid oid',
+  proallargtypes => '{oid,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{dboid,reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}',
+  prosrc => 'pg_stat_vacuum_tables' },
 ]
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 759f9a87d38..07b28b15d9f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -308,6 +308,7 @@ extern PGDLLIMPORT int vacuum_multixact_failsafe_age;
 extern PGDLLIMPORT pg_atomic_uint32 *VacuumSharedCostBalance;
 extern PGDLLIMPORT pg_atomic_uint32 *VacuumActiveNWorkers;
 extern PGDLLIMPORT int VacuumCostBalanceLocal;
+extern PGDLLIMPORT double VacuumDelayTime;
 
 extern PGDLLIMPORT bool VacuumFailsafeActive;
 extern PGDLLIMPORT double vacuum_cost_delay;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f63159c55ca..4492a0572c6 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -167,6 +167,52 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter sync_error_count;
 } PgStat_BackendSubEntry;
 
+/* ----------
+ *
+ * ExtVacReport
+ *
+ * Additional statistics of vacuum processing over a heap 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 ExtVacReport
+{
+	int64		total_blks_read; 	/* number of pages that were missed in shared buffers during a vacuum of specific relation */
+	int64		total_blks_hit; 	/* number of pages that were found in shared buffers during a vacuum of specific relation */
+	int64		total_blks_dirtied;	/* number of pages marked as 'Dirty' during a vacuum of specific relation. */
+	int64		total_blks_written;	/* number of pages written during a vacuum of specific relation. */
+
+	int64		blks_fetched; 		/* number of a relation blocks, fetched during the vacuum. */
+	int64		blks_hit;		/* number of a relation blocks, found in shared buffers during the vacuum. */
+
+	/* 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 */
+
+	/* 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		system_time;	/* amount of time the CPU was busy executing vacuum code in kernel space, in msec */
+	double		user_time;		/* amount of time the CPU was busy executing vacuum code in user space, in msec */
+	double		total_time;		/* total time of a vacuum operation, in msec */
+
+	/* Interruptions on any errors. */
+	int32		interrupts;
+
+	int64		pages_scanned;		/* number of pages we examined */
+	int64		pages_removed;		/* number of pages removed by vacuum */
+	int64		pages_frozen;		/* number of pages marked in VM as frozen */
+	int64		pages_all_visible;	/* number of pages marked in VM as all-visible */
+	int64		tuples_deleted;		/* tuples deleted by vacuum */
+	int64		tuples_frozen;		/* tuples frozen up by vacuum */
+	int64		dead_tuples;		/* number of deleted tuples which vacuum cannot clean up by vacuum operation */
+	int64		index_vacuum_count;	/* number of index vacuumings */
+} ExtVacReport;
+
 /* ----------
  * PgStat_TableCounts			The actual per-table counts kept by a backend
  *
@@ -207,6 +253,16 @@ typedef struct PgStat_TableCounts
 
 	PgStat_Counter blocks_fetched;
 	PgStat_Counter blocks_hit;
+
+	PgStat_Counter rev_all_visible_pages;
+	PgStat_Counter rev_all_frozen_pages;
+
+	/*
+	 * Additional cumulative stat on vacuum operations.
+	 * Use an expensive structure as an abstraction for different types of
+	 * relations.
+	 */
+	ExtVacReport	vacuum_ext;
 } PgStat_TableCounts;
 
 /* ----------
@@ -265,7 +321,7 @@ typedef struct PgStat_TableXactStatus
  * ------------------------------------------------------------
  */
 
-#define PGSTAT_FILE_FORMAT_ID	0x01A5BCAE
+#define PGSTAT_FILE_FORMAT_ID	0x01A5BCAF
 
 typedef struct PgStat_ArchiverStats
 {
@@ -384,6 +440,8 @@ typedef struct PgStat_StatDBEntry
 	PgStat_Counter sessions_killed;
 
 	TimestampTz stat_reset_timestamp;
+
+	ExtVacReport vacuum_ext;		/* extended vacuum statistics */
 } PgStat_StatDBEntry;
 
 typedef struct PgStat_StatFuncEntry
@@ -456,6 +514,11 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter analyze_count;
 	TimestampTz last_autoanalyze_time;	/* autovacuum initiated */
 	PgStat_Counter autoanalyze_count;
+
+	PgStat_Counter rev_all_visible_pages;
+	PgStat_Counter rev_all_frozen_pages;
+
+	ExtVacReport vacuum_ext;
 } PgStat_StatTabEntry;
 
 typedef struct PgStat_WalStats
@@ -621,10 +684,12 @@ extern void pgstat_assoc_relation(Relation rel);
 extern void pgstat_unlink_relation(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
-								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
+								 PgStat_Counter livetuples, PgStat_Counter deadtuples,
+								 ExtVacReport *params);
 extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
+extern void pgstat_report_vacuum_error(Oid tableoid);
 
 /*
  * If stats are enabled, but pending data hasn't been prepared yet, call
@@ -672,6 +737,17 @@ extern void pgstat_report_analyze(Relation rel,
 		if (pgstat_should_count_relation(rel))						\
 			(rel)->pgstat_info->counts.blocks_hit++;				\
 	} while (0)
+/* accumulate unfrozen all-visible and all-frozen pages */
+#define pgstat_count_vm_rev_all_visible(rel)						\
+	do {															\
+		if (pgstat_should_count_relation(rel))						\
+			(rel)->pgstat_info->counts.rev_all_visible_pages++;	\
+	} while (0)
+#define pgstat_count_vm_rev_all_frozen(rel)						\
+	do {															\
+		if (pgstat_should_count_relation(rel))						\
+			(rel)->pgstat_info->counts.rev_all_frozen_pages++;	\
+	} while (0)
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
 extern void pgstat_count_heap_update(Relation rel, bool hot, bool newpage);
@@ -688,7 +764,9 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
 extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared,
 														   Oid reloid);
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
-
+extern void
+pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,
+							   bool accumulate_reltype_specific_info);
 
 /*
  * Functions in pgstat_replslot.c
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 054dd2bf62f..c6225b9cddd 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -228,7 +228,7 @@ extern int	err_generic_string(int field, const char *str);
 extern int	geterrcode(void);
 extern int	geterrposition(void);
 extern int	getinternalerrposition(void);
-
+extern int	geterrelevel(void);
 
 /*----------
  * Old-style error reporting API: to be used in this way:
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index fb132e439dc..715ae1b6fd4 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -549,7 +549,7 @@ extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid,
 
 extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid);
 extern void pgstat_snapshot_fixed(PgStat_Kind kind);
-
+extern void pgstat_update_snapshot(PgStat_Kind kind);
 
 /*
  * Functions in pgstat_archiver.c
@@ -874,4 +874,38 @@ pgstat_get_custom_snapshot_data(PgStat_Kind kind)
 	return pgStatLocal.snapshot.custom_data[idx];
 }
 
+/* hash table for statistics snapshots entry */
+typedef struct PgStat_SnapshotEntry
+{
+	PgStat_HashKey key;
+	char		status;			/* for simplehash use */
+	void	   *data;			/* the stats data itself */
+} PgStat_SnapshotEntry;
+
+/* ----------
+ * Backend-local Hash Table Definitions
+ * ----------
+ */
+
+/* for stats snapshot entries */
+#define SH_PREFIX pgstat_snapshot
+#define SH_ELEMENT_TYPE PgStat_SnapshotEntry
+#define SH_KEY_TYPE PgStat_HashKey
+#define SH_KEY key
+#define SH_HASH_KEY(tb, key) \
+	pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
+#define SH_EQUAL(tb, a, b) \
+	pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0
+#define SH_SCOPE static inline
+#define SH_DEFINE
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+typedef pgstat_snapshot_iterator SnapshotIterator;
+
+#define InitSnapshotIterator(htable, iter) \
+	pgstat_snapshot_start_iterate(htable, iter);
+#define ScanStatSnapshot(htable, iter) \
+	pgstat_snapshot_iterate(htable, iter)
+
 #endif							/* PGSTAT_INTERNAL_H */
diff --git a/src/test/isolation/expected/vacuum-extending-in-repetable-read.out b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
new file mode 100644
index 00000000000..7cdb79c0ec4
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
@@ -0,0 +1,53 @@
+unused step name: s2_delete
+Parsed test spec with 2 sessions
+
+starting permutation: s2_insert s2_print_vacuum_stats_table s1_begin_repeatable_read s2_update s2_insert_interrupt s2_vacuum s2_print_vacuum_stats_table s1_commit s2_checkpoint s2_vacuum s2_print_vacuum_stats_table
+step s2_insert: INSERT INTO test_vacuum_stat_isolation(id, ival) SELECT ival, ival%10 FROM generate_series(1,1000) As ival;
+step s2_print_vacuum_stats_table: 
+    SELECT
+    vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen
+    FROM pg_stat_vacuum_tables vt, pg_class c
+    WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid;
+
+relname                   |tuples_deleted|dead_tuples|tuples_frozen
+--------------------------+--------------+-----------+-------------
+test_vacuum_stat_isolation|             0|          0|            0
+(1 row)
+
+step s1_begin_repeatable_read: 
+  BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
+  select count(ival) from test_vacuum_stat_isolation where id>900;
+
+count
+-----
+  100
+(1 row)
+
+step s2_update: UPDATE test_vacuum_stat_isolation SET ival = ival + 2 where id > 900;
+step s2_insert_interrupt: INSERT INTO test_vacuum_stat_isolation values (1,1);
+step s2_vacuum: VACUUM test_vacuum_stat_isolation;
+step s2_print_vacuum_stats_table: 
+    SELECT
+    vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen
+    FROM pg_stat_vacuum_tables vt, pg_class c
+    WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid;
+
+relname                   |tuples_deleted|dead_tuples|tuples_frozen
+--------------------------+--------------+-----------+-------------
+test_vacuum_stat_isolation|             0|        100|            0
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_checkpoint: CHECKPOINT;
+step s2_vacuum: VACUUM test_vacuum_stat_isolation;
+step s2_print_vacuum_stats_table: 
+    SELECT
+    vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen
+    FROM pg_stat_vacuum_tables vt, pg_class c
+    WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid;
+
+relname                   |tuples_deleted|dead_tuples|tuples_frozen
+--------------------------+--------------+-----------+-------------
+test_vacuum_stat_isolation|           100|        100|          101
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 6da98cffaca..c612de70083 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -95,6 +95,7 @@ test: timeouts
 test: vacuum-concurrent-drop
 test: vacuum-conflict
 test: vacuum-skip-locked
+test: vacuum-extending-in-repetable-read
 test: stats
 test: horizons
 test: predicate-hash
diff --git a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
new file mode 100644
index 00000000000..7d31ddbece9
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -0,0 +1,51 @@
+# Test for checking dead_tuples, tuples_deleted and frozen tuples in pg_stat_vacuum_tables.
+# Dead_tuples values are counted when vacuum cannot clean up unused tuples while lock is using another transaction.
+# Dead_tuples aren't increased after releasing lock compared with tuples_deleted, which increased
+# by the value of the cleared tuples that the vacuum managed to clear.
+
+setup
+{
+    CREATE TABLE test_vacuum_stat_isolation(id int, ival int) WITH (autovacuum_enabled = off);
+    SET track_io_timing = on;
+}
+
+teardown
+{
+    DROP TABLE test_vacuum_stat_isolation CASCADE;
+    RESET track_io_timing;
+}
+
+session s1
+step s1_begin_repeatable_read   {
+  BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
+  select count(ival) from test_vacuum_stat_isolation where id>900;
+  }
+step s1_commit                  { COMMIT; }
+
+session s2
+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; }
+step s2_insert_interrupt        { INSERT INTO test_vacuum_stat_isolation values (1,1); }
+step s2_vacuum                  { VACUUM test_vacuum_stat_isolation; }
+step s2_checkpoint              { CHECKPOINT; }
+step s2_print_vacuum_stats_table
+{
+    SELECT
+    vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen
+    FROM pg_stat_vacuum_tables vt, pg_class c
+    WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid;
+}
+
+permutation
+    s2_insert
+    s2_print_vacuum_stats_table
+    s1_begin_repeatable_read
+    s2_update
+    s2_insert_interrupt
+    s2_vacuum
+    s2_print_vacuum_stats_table
+    s1_commit
+    s2_checkpoint
+    s2_vacuum
+    s2_print_vacuum_stats_table
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 0d734169f11..9ae743eae0c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -32,9 +32,10 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
        prokind NOT IN ('f', 'a', 'w', 'p') OR
        provolatile NOT IN ('i', 's', 'v') OR
        proparallel NOT IN ('s', 'r', 'u');
- oid | proname 
------+---------
-(0 rows)
+ oid  |        proname        
+------+-----------------------
+ 8001 | pg_stat_vacuum_tables
+(1 row)
 
 -- prosrc should never be null; it can be empty only if prosqlbody isn't null
 SELECT p1.oid, p1.proname
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 862433ee52b..6e8790f66f6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2229,6 +2229,40 @@ pg_stat_user_tables| SELECT relid,
     autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_tables| SELECT rel.oid AS relid,
+    ns.nspname AS schema,
+    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_scanned,
+    stats.pages_removed,
+    stats.pages_frozen,
+    stats.pages_all_visible,
+    stats.tuples_deleted,
+    stats.tuples_frozen,
+    stats.dead_tuples,
+    stats.index_vacuum_count,
+    stats.rev_all_frozen_pages,
+    stats.rev_all_visible_pages,
+    stats.wal_records,
+    stats.wal_fpi,
+    stats.wal_bytes,
+    stats.blk_read_time,
+    stats.blk_write_time,
+    stats.delay_time,
+    stats.system_time,
+    stats.user_time,
+    stats.total_time,
+    stats.interrupts
+   FROM pg_database db,
+    pg_class rel,
+    pg_namespace ns,
+    LATERAL pg_stat_vacuum_tables(db.oid, 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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts)
+  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
     wal_bytes,
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_statistics.out
new file mode 100644
index 00000000000..1a7d04b0590
--- /dev/null
+++ b/src/test/regress/expected/vacuum_tables_statistics.out
@@ -0,0 +1,200 @@
+--
+-- Test cumulative vacuum stats system
+--
+-- Check the wall statistics collected during vacuum operation:
+-- number of frozen and visible pages set by vacuum;
+-- 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 
+--------------
+ on
+(1 row)
+
+-- 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 
+--------------------------
+ 
+(1 row)
+
+\set sample_size 10000
+SET vacuum_freeze_min_age = 0;
+SET vacuum_freeze_table_age = 0;
+--SET stats_fetch_consistency = snapshot;
+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;
+SELECT oid AS roid from pg_class where relname = 'vestat' \gset
+DELETE FROM vestat WHERE x % 2 = 0;
+-- Before the first vacuum execution extended stats view is empty.
+SELECT vt.relname,pages_frozen,tuples_deleted,relpages,pages_scanned,pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+ relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
+---------+--------------+----------------+----------+---------------+---------------
+ vestat  |            0 |              0 |      455 |             0 |             0
+(1 row)
+
+SELECT relpages AS rp
+FROM pg_class c
+WHERE relname = 'vestat' \gset
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- The table and index extended vacuum statistics should show us that
+-- vacuum frozed pages and clean up pages, but pages_removed stayed the same
+-- because of not full table have cleaned up
+SELECT vt.relname,pages_frozen > 0 AS pages_frozen,tuples_deleted > 0 AS tuples_deleted,relpages-:rp = 0 AS relpages,pages_scanned > 0 AS pages_scanned,pages_removed = 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+ relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
+---------+--------------+----------------+----------+---------------+---------------
+ vestat  | f            | t              | t        | t             | t
+(1 row)
+
+SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+-- Look into WAL records deltas.
+SELECT wal_records > 0 AS dWR, wal_bytes > 0 AS dWB
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+ dwr | dwb 
+-----+-----
+ t   | t
+(1 row)
+
+DELETE FROM vestat;;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- pages_removed must be increased
+SELECT vt.relname,pages_frozen-:fp > 0 AS pages_frozen,tuples_deleted-:td > 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps > 0 AS pages_scanned,pages_removed-:pr > 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+ relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
+---------+--------------+----------------+----------+---------------+---------------
+ vestat  | f            | t              | f        | t             | t
+(1 row)
+
+SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records-:hwr AS dwr, wal_bytes-:hwb AS dwb, wal_fpi-:hfpi AS dfpi
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+-- WAL advance should be detected.
+SELECT :dwr > 0 AS dWR, :dwb > 0 AS dWB;
+ dwr | dwb 
+-----+-----
+ t   | t
+(1 row)
+
+-- Store WAL advances into variables
+SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+DELETE FROM vestat WHERE x % 2 = 0;
+-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics
+-- are detected here.
+VACUUM FULL vestat;
+-- It is necessary to check the wal statistics
+CHECKPOINT;
+-- Store WAL advances into variables
+SELECT wal_records-:hwr AS dwr2, wal_bytes-:hwb AS dwb2, wal_fpi-:hfpi AS dfpi2
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+-- WAL and other statistics advance should not be detected.
+SELECT :dwr2=0 AS dWR, :dfpi2=0 AS dFPI, :dwb2=0 AS dWB;
+ dwr | dfpi | dwb 
+-----+------+-----
+ t   | t    | t
+(1 row)
+
+SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp < 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+ relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
+---------+--------------+----------------+----------+---------------+---------------
+ vestat  | t            | t              | f        | t             | t
+(1 row)
+
+SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps,pages_removed AS pr
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+DELETE FROM vestat;
+TRUNCATE vestat;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- Store WAL advances into variables after removing all tuples from the table
+SELECT wal_records-:hwr AS dwr3, wal_bytes-:hwb AS dwb3, wal_fpi-:hfpi AS dfpi3
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+--There are nothing changed
+SELECT :dwr3>0 AS dWR, :dfpi3=0 AS dFPI, :dwb3>0 AS dWB;
+ dwr | dfpi | dwb 
+-----+------+-----
+ t   | t    | t
+(1 row)
+
+--
+-- Now, the table and index is compressed into zero number of pages. Check it
+-- in vacuum extended statistics.
+-- The pages_frozen, pages_scanned values shouldn't be changed
+--
+SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+ relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
+---------+--------------+----------------+----------+---------------+---------------
+ vestat  | t            | t              | f        | t             | t
+(1 row)
+
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+-- must be empty
+SELECT pages_frozen, pages_all_visible, rev_all_frozen_pages,rev_all_visible_pages
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+ pages_frozen | pages_all_visible | rev_all_frozen_pages | rev_all_visible_pages 
+--------------+-------------------+----------------------+-----------------------
+            0 |                 0 |                    0 |                     0
+(1 row)
+
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+-- backend defreezed pages
+SELECT pages_frozen > 0 AS pages_frozen,pages_all_visible > 0 AS pages_all_visible,rev_all_frozen_pages = 0 AS rev_all_frozen_pages,rev_all_visible_pages = 0 AS rev_all_visible_pages
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+ pages_frozen | pages_all_visible | rev_all_frozen_pages | rev_all_visible_pages 
+--------------+-------------------+----------------------+-----------------------
+ f            | f                 | t                    | t
+(1 row)
+
+SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+UPDATE vestat SET x = x+1001;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+SELECT pages_frozen > :pf AS pages_frozen,pages_all_visible > :pv AS pages_all_visible,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 WHERE relname = 'vestat';
+ pages_frozen | pages_all_visible | rev_all_frozen_pages | rev_all_visible_pages 
+--------------+-------------------+----------------------+-----------------------
+ f            | f                 | f                    | f
+(1 row)
+
+SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+-- vacuum freezed pages
+SELECT pages_frozen = :pf AS pages_frozen,pages_all_visible = :pv AS pages_all_visible,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 WHERE relname = 'vestat';
+ pages_frozen | pages_all_visible | rev_all_frozen_pages | rev_all_visible_pages 
+--------------+-------------------+----------------------+-----------------------
+ t            | t                 | t                    | t
+(1 row)
+
+DROP TABLE vestat CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 2429ec2bbaa..f8a4bcccc9d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -136,3 +136,8 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# ----------
+# Check vacuum statistics
+# ----------
+test: vacuum_tables_statistics
\ No newline at end of file
diff --git a/src/test/regress/sql/vacuum_tables_statistics.sql b/src/test/regress/sql/vacuum_tables_statistics.sql
new file mode 100644
index 00000000000..41e387dd304
--- /dev/null
+++ b/src/test/regress/sql/vacuum_tables_statistics.sql
@@ -0,0 +1,158 @@
+--
+-- Test cumulative vacuum stats system
+--
+-- Check the wall statistics collected during vacuum operation:
+-- number of frozen and visible pages set by vacuum;
+-- 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
+-- 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();
+
+\set sample_size 10000
+SET vacuum_freeze_min_age = 0;
+SET vacuum_freeze_table_age = 0;
+--SET stats_fetch_consistency = snapshot;
+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;
+
+SELECT oid AS roid from pg_class where relname = 'vestat' \gset
+
+DELETE FROM vestat WHERE x % 2 = 0;
+-- Before the first vacuum execution extended stats view is empty.
+SELECT vt.relname,pages_frozen,tuples_deleted,relpages,pages_scanned,pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+SELECT relpages AS rp
+FROM pg_class c
+WHERE relname = 'vestat' \gset
+
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+
+-- The table and index extended vacuum statistics should show us that
+-- vacuum frozed pages and clean up pages, but pages_removed stayed the same
+-- because of not full table have cleaned up
+SELECT vt.relname,pages_frozen > 0 AS pages_frozen,tuples_deleted > 0 AS tuples_deleted,relpages-:rp = 0 AS relpages,pages_scanned > 0 AS pages_scanned,pages_removed = 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset
+
+-- Store WAL advances into variables
+SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+-- Look into WAL records deltas.
+SELECT wal_records > 0 AS dWR, wal_bytes > 0 AS dWB
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+
+DELETE FROM vestat;;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+
+-- pages_removed must be increased
+SELECT vt.relname,pages_frozen-:fp > 0 AS pages_frozen,tuples_deleted-:td > 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps > 0 AS pages_scanned,pages_removed-:pr > 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset
+
+-- Store WAL advances into variables
+SELECT wal_records-:hwr AS dwr, wal_bytes-:hwb AS dwb, wal_fpi-:hfpi AS dfpi
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+-- WAL advance should be detected.
+SELECT :dwr > 0 AS dWR, :dwb > 0 AS dWB;
+
+-- Store WAL advances into variables
+SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+DELETE FROM vestat WHERE x % 2 = 0;
+-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics
+-- are detected here.
+VACUUM FULL vestat;
+-- It is necessary to check the wal statistics
+CHECKPOINT;
+
+-- Store WAL advances into variables
+SELECT wal_records-:hwr AS dwr2, wal_bytes-:hwb AS dwb2, wal_fpi-:hfpi AS dfpi2
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+-- WAL and other statistics advance should not be detected.
+SELECT :dwr2=0 AS dWR, :dfpi2=0 AS dFPI, :dwb2=0 AS dWB;
+
+SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp < 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps,pages_removed AS pr
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset
+
+-- Store WAL advances into variables
+SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+DELETE FROM vestat;
+TRUNCATE vestat;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+
+-- Store WAL advances into variables after removing all tuples from the table
+SELECT wal_records-:hwr AS dwr3, wal_bytes-:hwb AS dwb3, wal_fpi-:hfpi AS dfpi3
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+--There are nothing changed
+SELECT :dwr3>0 AS dWR, :dfpi3=0 AS dFPI, :dwb3>0 AS dWB;
+
+--
+-- Now, the table and index is compressed into zero number of pages. Check it
+-- in vacuum extended statistics.
+-- The pages_frozen, pages_scanned values shouldn't be changed
+--
+SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed
+FROM pg_stat_vacuum_tables vt, pg_class c
+WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
+
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+
+-- must be empty
+SELECT pages_frozen, pages_all_visible, rev_all_frozen_pages,rev_all_visible_pages
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+
+-- backend defreezed pages
+SELECT pages_frozen > 0 AS pages_frozen,pages_all_visible > 0 AS pages_all_visible,rev_all_frozen_pages = 0 AS rev_all_frozen_pages,rev_all_visible_pages = 0 AS rev_all_visible_pages
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+UPDATE vestat SET x = x+1001;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+
+SELECT pages_frozen > :pf AS pages_frozen,pages_all_visible > :pv AS pages_all_visible,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 WHERE relname = 'vestat';
+SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
+FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
+
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
+
+-- vacuum freezed pages
+SELECT pages_frozen = :pf AS pages_frozen,pages_all_visible = :pv AS pages_all_visible,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 WHERE relname = 'vestat';
+
+DROP TABLE vestat CASCADE;
\ No newline at end of file
-- 
2.34.1

From 55368c92223b944331c1b18fbbf09e410ac4f478 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 11 Jun 2024 10:11:37 +0300
Subject: [PATCH 2/4] Machinery for grabbing an extended vacuum statistics on
 heap and index relations. Remember, statistic on heap and index relations a
 bit different (see ExtVacReport to find out more information). The concept of
 the ExtVacReport structure has been complicated to store statistic
 information for two kinds of relations: for heap and index relations.
 ExtVacReportType variable helps to determine what the kind is considering
 now.

---
 src/backend/access/heap/vacuumlazy.c          |  99 +++++++++--
 src/backend/catalog/system_views.sql          |  41 +++++
 src/backend/utils/activity/pgstat.c           |  45 +++--
 src/backend/utils/activity/pgstat_relation.c  |   3 +-
 src/backend/utils/adt/pgstatfuncs.c           | 108 +++++++-----
 src/include/catalog/pg_proc.dat               |   9 +
 src/include/pgstat.h                          |  53 ++++--
 .../vacuum-extending-in-repetable-read.out    |   7 +-
 .../vacuum-extending-in-repetable-read.spec   |   2 +-
 src/test/regress/expected/opr_sanity.out      |   7 +-
 src/test/regress/expected/rules.out           |  26 +++
 .../expected/vacuum_index_statistics.out      | 158 ++++++++++++++++++
 .../expected/vacuum_tables_statistics.out     |   3 +-
 src/test/regress/parallel_schedule            |   1 +
 .../regress/sql/vacuum_index_statistics.sql   | 128 ++++++++++++++
 15 files changed, 606 insertions(+), 84 deletions(-)
 create mode 100644 src/test/regress/expected/vacuum_index_statistics.out
 create mode 100644 src/test/regress/sql/vacuum_index_statistics.sql

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3941ae26f2d..4e2ae78d255 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -168,6 +168,7 @@ typedef struct LVRelState
 	char	   *dbname;
 	char	   *relnamespace;
 	Oid			reloid;
+	Oid			indoid;
 	char	   *relname;
 	char	   *indname;		/* Current index name */
 	BlockNumber blkno;			/* used only for heap operations */
@@ -246,6 +247,13 @@ typedef struct LVExtStatCounters
 	PgStat_Counter blocks_hit;
 } LVExtStatCounters;
 
+typedef struct LVExtStatCountersIdx
+{
+	LVExtStatCounters common;
+	int64		pages_deleted;
+	int64		tuples_removed;
+} LVExtStatCountersIdx;
+
 /* non-export function prototypes */
 static void lazy_scan_heap(LVRelState *vacrel);
 static bool heap_vac_scan_next_block(LVRelState *vacrel, BlockNumber *blkno,
@@ -408,6 +416,46 @@ extvac_stats_end(Relation rel, LVExtStatCounters *counters,
 		rel->pgstat_info->counts.blocks_hit - counters->blocks_hit;
 }
 
+static void
+extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
+					   LVExtStatCountersIdx *counters)
+{
+	extvac_stats_start(rel, &counters->common);
+	counters->pages_deleted = counters->tuples_removed = 0;
+
+	if (stats != NULL)
+	{
+		/*
+		 * XXX: Why do we need this code here? If it is needed, I feel lack of
+		 * comments, describing the reason.
+		 */
+		counters->tuples_removed = stats->tuples_removed;
+		counters->pages_deleted = stats->pages_deleted;
+	}
+}
+
+static void
+extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
+					 LVExtStatCountersIdx *counters, ExtVacReport *report)
+{
+	extvac_stats_end(rel, &counters->common, report);
+	report->type = PGSTAT_EXTVAC_INDEX;
+
+	if (stats != NULL)
+	{
+		/*
+		 * if something goes wrong or an user doesn't want to track a database
+		 * activity - just suppress it.
+		 */
+
+		/* Fill index-specific extended stats fields */
+		report->index.tuples_deleted =
+							stats->tuples_removed - counters->tuples_removed;
+		report->index.pages_deleted =
+							stats->pages_deleted - counters->pages_deleted;
+	}
+}
+
 /*
  *	heap_vacuum_rel() -- perform VACUUM for one heap relation
  *
@@ -711,14 +759,15 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	extvac_stats_end(rel, &extVacCounters, &extVacReport);
 
 	/* Fill heap-specific extended stats fields */
-	extVacReport.pages_scanned = vacrel->scanned_pages;
-	extVacReport.pages_removed = vacrel->removed_pages;
-	extVacReport.pages_frozen = vacrel->set_frozen_pages;
-	extVacReport.pages_all_visible = vacrel->set_all_visible_pages;
-	extVacReport.tuples_deleted = vacrel->tuples_deleted;
-	extVacReport.tuples_frozen = vacrel->tuples_frozen;
-	extVacReport.dead_tuples = vacrel->recently_dead_tuples + vacrel->missed_dead_tuples;
-	extVacReport.index_vacuum_count = vacrel->num_index_scans;
+	extVacReport.type = PGSTAT_EXTVAC_HEAP;
+	extVacReport.heap.pages_scanned = vacrel->scanned_pages;
+	extVacReport.heap.pages_removed = vacrel->removed_pages;
+	extVacReport.heap.pages_frozen = vacrel->set_frozen_pages;
+	extVacReport.heap.pages_all_visible = vacrel->set_all_visible_pages;
+	extVacReport.heap.tuples_deleted = vacrel->tuples_deleted;
+	extVacReport.heap.tuples_frozen = vacrel->tuples_frozen;
+	extVacReport.heap.dead_tuples = vacrel->recently_dead_tuples + vacrel->missed_dead_tuples;
+	extVacReport.heap.index_vacuum_count = vacrel->num_index_scans;
 
 	/*
 	 * Report results to the cumulative stats system, too.
@@ -2583,6 +2632,10 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 {
 	IndexVacuumInfo ivinfo;
 	LVSavedErrInfo saved_err_info;
+	LVExtStatCountersIdx extVacCounters;
+	ExtVacReport extVacReport;
+
+	extvac_stats_start_idx(indrel, istat, &extVacCounters);
 
 	ivinfo.index = indrel;
 	ivinfo.heaprel = vacrel->rel;
@@ -2601,6 +2654,7 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	 */
 	Assert(vacrel->indname == NULL);
 	vacrel->indname = pstrdup(RelationGetRelationName(indrel));
+	vacrel->indoid = RelationGetRelid(indrel);
 	update_vacuum_error_info(vacrel, &saved_err_info,
 							 VACUUM_ERRCB_PHASE_VACUUM_INDEX,
 							 InvalidBlockNumber, InvalidOffsetNumber);
@@ -2609,6 +2663,13 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	istat = vac_bulkdel_one_index(&ivinfo, istat, (void *) vacrel->dead_items,
 								  vacrel->dead_items_info);
 
+	/* Make extended vacuum stats report for index */
+	extvac_stats_end_idx(indrel, istat, &extVacCounters, &extVacReport);
+
+	pgstat_report_vacuum(RelationGetRelid(indrel),
+							indrel->rd_rel->relisshared,
+							0, 0, &extVacReport);
+
 	/* Revert to the previous phase information for error traceback */
 	restore_vacuum_error_info(vacrel, &saved_err_info);
 	pfree(vacrel->indname);
@@ -2633,6 +2694,10 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 {
 	IndexVacuumInfo ivinfo;
 	LVSavedErrInfo saved_err_info;
+	LVExtStatCountersIdx extVacCounters;
+	ExtVacReport extVacReport;
+
+	extvac_stats_start_idx(indrel, istat, &extVacCounters);
 
 	ivinfo.index = indrel;
 	ivinfo.heaprel = vacrel->rel;
@@ -2652,12 +2717,20 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	 */
 	Assert(vacrel->indname == NULL);
 	vacrel->indname = pstrdup(RelationGetRelationName(indrel));
+	vacrel->indoid = RelationGetRelid(indrel);
 	update_vacuum_error_info(vacrel, &saved_err_info,
 							 VACUUM_ERRCB_PHASE_INDEX_CLEANUP,
 							 InvalidBlockNumber, InvalidOffsetNumber);
 
 	istat = vac_cleanup_one_index(&ivinfo, istat);
 
+	/* Make extended vacuum stats report for index */
+	extvac_stats_end_idx(indrel, istat, &extVacCounters, &extVacReport);
+
+	pgstat_report_vacuum(RelationGetRelid(indrel),
+							indrel->rd_rel->relisshared,
+							0, 0, &extVacReport);
+
 	/* Revert to the previous phase information for error traceback */
 	restore_vacuum_error_info(vacrel, &saved_err_info);
 	pfree(vacrel->indname);
@@ -3274,7 +3347,7 @@ vacuum_error_callback(void *arg)
 	{
 		case VACUUM_ERRCB_PHASE_SCAN_HEAP:
 			if(geterrelevel() >= ERROR)
-				pgstat_report_vacuum_error(errinfo->reloid);
+				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
 				if (OffsetNumberIsValid(errinfo->offnum))
@@ -3291,7 +3364,7 @@ vacuum_error_callback(void *arg)
 
 		case VACUUM_ERRCB_PHASE_VACUUM_HEAP:
 			if(geterrelevel() >= ERROR)
-				pgstat_report_vacuum_error(errinfo->reloid);
+				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
 				if (OffsetNumberIsValid(errinfo->offnum))
@@ -3307,16 +3380,22 @@ 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_HEAP);
 			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 68e6bfe6115..05f8fc07108 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1425,3 +1425,44 @@ WHERE
   db.datname = current_database() AND
   rel.oid = stats.relid AND
   ns.oid = rel.relnamespace;
+
+CREATE VIEW pg_stat_vacuum_indexes AS
+SELECT
+  rel.oid as relid,
+  ns.nspname AS "schema",
+  rel.relname AS 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.system_time,
+  stats.user_time,
+  stats.total_time,
+
+  stats.interrupts
+FROM
+  pg_database db,
+  pg_class rel,
+  pg_namespace ns,
+  pg_stat_vacuum_indexes(db.oid, rel.oid) stats
+WHERE
+  db.datname = current_database() AND
+  rel.oid = stats.relid AND
+  ns.oid = rel.relnamespace;
+
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 6a788f2b586..75890b18988 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -824,17 +824,33 @@ pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,
 	if (!accumulate_reltype_specific_info)
 		return;
 
-	dst->blks_fetched += src->blks_fetched;
-	dst->blks_hit += src->blks_hit;
-
-	dst->pages_scanned += src->pages_scanned;
-	dst->pages_removed += src->pages_removed;
-	dst->pages_frozen += src->pages_frozen;
-	dst->pages_all_visible += src->pages_all_visible;
-	dst->tuples_deleted += src->tuples_deleted;
-	dst->tuples_frozen += src->tuples_frozen;
-	dst->dead_tuples += src->dead_tuples;
-	dst->index_vacuum_count += src->index_vacuum_count;
+	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_HEAP)
+		{
+			dst->heap.pages_scanned += src->heap.pages_scanned;
+			dst->heap.pages_removed += src->heap.pages_removed;
+			dst->heap.pages_frozen += src->heap.pages_frozen;
+			dst->heap.pages_all_visible += src->heap.pages_all_visible;
+			dst->heap.tuples_deleted += src->heap.tuples_deleted;
+			dst->heap.tuples_frozen += src->heap.tuples_frozen;
+			dst->heap.dead_tuples += src->heap.dead_tuples;
+			dst->heap.index_vacuum_count += src->heap.index_vacuum_count;
+		}
+		else if (dst->type == PGSTAT_EXTVAC_INDEX)
+		{
+			dst->index.pages_deleted += src->index.pages_deleted;
+			dst->index.tuples_deleted += src->index.tuples_deleted;
+		}
+	}
 }
 
 /* ------------------------------------------------------------
@@ -1081,7 +1097,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
 	PG_TRY();
 	{
 		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
-		pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+		if (kind == PGSTAT_KIND_RELATION)
+			pgstat_build_snapshot(PGSTAT_KIND_RELATION);
 	}
 	PG_FINALLY();
 	{
@@ -1136,6 +1153,10 @@ pgstat_build_snapshot(PgStat_Kind statKind)
 		if (p->dropped)
 			continue;
 
+		if (statKind != PGSTAT_KIND_INVALID && statKind != p->key.kind)
+			/* Load stat of specific type, if defined */
+			continue;
+
 		Assert(pg_atomic_read_u32(&p->refcount) > 0);
 
 		stats_data = dsa_get_address(pgStatLocal.dsa, p->body);
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index d40d43cdb4a..5b06b04faad 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -211,7 +211,7 @@ pgstat_drop_relation(Relation rel)
  * ---------
  */
 void
-pgstat_report_vacuum_error(Oid tableoid)
+pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Relation *shtabentry;
@@ -228,6 +228,7 @@ pgstat_report_vacuum_error(Oid tableoid)
 	tabentry = &shtabentry->stats;
 
 	tabentry->vacuum_ext.interrupts++;
+	tabentry->vacuum_ext.type = m_type;
 	pgstat_unlock_entry(entry_ref);
 }
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f94e562009d..b868b917ceb 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2035,6 +2035,8 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 }
 
 #define EXTVACHEAPSTAT_COLUMNS	27
+#define EXTVACIDXSTAT_COLUMNS	19
+#define EXTVACSTAT_COLUMNS Max(EXTVACHEAPSTAT_COLUMNS, EXTVACIDXSTAT_COLUMNS)
 
 static Oid CurrentDatabaseId = InvalidOid;
 
@@ -2078,12 +2080,12 @@ static void
 tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
 			   TupleDesc tupdesc, PgStat_StatTabEntry *tabentry, int ncolumns)
 {
-	Datum		values[EXTVACHEAPSTAT_COLUMNS];
-	bool		nulls[EXTVACHEAPSTAT_COLUMNS];
+	Datum		values[EXTVACSTAT_COLUMNS];
+	bool		nulls[EXTVACSTAT_COLUMNS];
 	char		buf[256];
 	int			i = 0;
 
-	memset(nulls, 0, EXTVACHEAPSTAT_COLUMNS * sizeof(bool));
+	memset(nulls, 0, EXTVACSTAT_COLUMNS * sizeof(bool));
 
 	values[i++] = ObjectIdGetDatum(relid);
 
@@ -2096,16 +2098,25 @@ tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
 									tabentry->vacuum_ext.blks_hit);
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_hit);
 
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_scanned);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_removed);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_frozen);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_all_visible);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.tuples_deleted);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.tuples_frozen);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.dead_tuples);
-	values[i++] = Int64GetDatum(tabentry->vacuum_ext.index_vacuum_count);
-	values[i++] = Int64GetDatum(tabentry->rev_all_frozen_pages);
-	values[i++] = Int64GetDatum(tabentry->rev_all_visible_pages);
+	if (tabentry->vacuum_ext.type == PGSTAT_EXTVAC_HEAP)
+	{
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.pages_scanned);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.pages_removed);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.pages_frozen);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.pages_all_visible);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.tuples_deleted);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.tuples_frozen);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.dead_tuples);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.heap.index_vacuum_count);
+		values[i++] = Int64GetDatum(tabentry->rev_all_frozen_pages);
+		values[i++] = Int64GetDatum(tabentry->rev_all_visible_pages);
+
+	}
+	else if (tabentry->vacuum_ext.type == PGSTAT_EXTVAC_INDEX)
+	{
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.index.pages_deleted);
+		values[i++] = Int64GetDatum(tabentry->vacuum_ext.index.tuples_deleted);
+	}
 
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_records);
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_fpi);
@@ -2134,7 +2145,7 @@ tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
  * Get the vacuum statistics for the heap tables or indexes.
  */
 static Datum
-pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
+pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 {
 	ReturnSetInfo		   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	MemoryContext			per_query_ctx;
@@ -2142,7 +2153,6 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 	Tuplestorestate		   *tupstore;
 	TupleDesc				tupdesc;
 	Oid						dbid = PG_GETARG_OID(0);
-	Oid						relid = PG_GETARG_OID(1);
 	PgStat_StatTabEntry    *tabentry;
 
 	InitMaterializedSRF(fcinfo, 0);
@@ -2169,40 +2179,45 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 
 	MemoryContextSwitchTo(oldcontext);
 
-	/* Load table statistics for specified database. */
-	if (OidIsValid(relid))
+	if (type == PGSTAT_EXTVAC_INDEX || type == PGSTAT_EXTVAC_HEAP)
 	{
-		tabentry = fetch_dbstat_tabentry(dbid, relid);
-		if (tabentry == NULL)
-			/* Table don't exists or isn't an heap relation. */
-			PG_RETURN_NULL();
+		Oid					relid = PG_GETARG_OID(1);
 
-		tuplestore_put_for_relation(relid, tupstore, tupdesc, tabentry, ncolumns);
-	}
-	else
-	{
-		SnapshotIterator		hashiter;
-		PgStat_SnapshotEntry   *entry;
-		Oid						storedMyDatabaseId = MyDatabaseId;
+		/* Load table statistics for specified database. */
+		if (OidIsValid(relid))
+		{
+			tabentry = fetch_dbstat_tabentry(dbid, relid);
+			if (tabentry == NULL || tabentry->vacuum_ext.type != type)
+				/* Table don't exists or isn't an heap relation. */
+				PG_RETURN_NULL();
 
-		pgstat_update_snapshot(PGSTAT_KIND_RELATION);
-		MyDatabaseId = storedMyDatabaseId;
+			tuplestore_put_for_relation(relid, tupstore, tupdesc, tabentry, ncolumns);
+		}
+		else
+		{
+			SnapshotIterator		hashiter;
+			PgStat_SnapshotEntry   *entry;
+			Oid						storedMyDatabaseId = MyDatabaseId;
 
+			pgstat_update_snapshot(PGSTAT_KIND_RELATION);
+			MyDatabaseId = storedMyDatabaseId;
 
-		/* Iterate the snapshot */
-		InitSnapshotIterator(pgStatLocal.snapshot.stats, &hashiter);
 
-		while ((entry = ScanStatSnapshot(pgStatLocal.snapshot.stats, &hashiter)) != NULL)
-		{
-			Oid	reloid;
+			/* Iterate the snapshot */
+			InitSnapshotIterator(pgStatLocal.snapshot.stats, &hashiter);
+
+			while ((entry = ScanStatSnapshot(pgStatLocal.snapshot.stats, &hashiter)) != NULL)
+			{
+				Oid	reloid;
 
-			CHECK_FOR_INTERRUPTS();
+				CHECK_FOR_INTERRUPTS();
 
-			tabentry = (PgStat_StatTabEntry *) entry->data;
-			reloid = entry->key.objoid;
+				tabentry = (PgStat_StatTabEntry *) entry->data;
+				reloid = entry->key.objoid;
 
-			if (tabentry != NULL)
-				tuplestore_put_for_relation(reloid, tupstore, tupdesc, tabentry, ncolumns);
+				if (tabentry != NULL && tabentry->vacuum_ext.type == type)
+					tuplestore_put_for_relation(reloid, tupstore, tupdesc, tabentry, ncolumns);
+			}
 		}
 	}
 	PG_RETURN_NULL();
@@ -2214,7 +2229,18 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 Datum
 pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 {
-	return pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
+	return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Get the vacuum statistics for the indexes.
+ */
+Datum
+pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
+{
+	return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
 	PG_RETURN_NULL();
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 443c4ec65d3..2e80a2502cc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12263,4 +12263,13 @@
   proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{dboid,reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}',
   prosrc => 'pg_stat_vacuum_tables' },
+{ oid => '8002',
+  descr => 'pg_stat_vacuum_indexes return stats values',
+  proname => 'pg_stat_vacuum_indexes', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proretset => 't',
+  proargtypes => 'oid oid',
+  proallargtypes => '{oid,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{dboid,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,system_time,user_time,total_time,interrupts}',
+  prosrc => 'pg_stat_vacuum_indexes' }
 ]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 4492a0572c6..762b53b88ed 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -167,11 +167,20 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter sync_error_count;
 } PgStat_BackendSubEntry;
 
+
+/* Type of ExtVacReport */
+typedef enum ExtVacReportType
+{
+	PGSTAT_EXTVAC_INVALID = 0,
+	PGSTAT_EXTVAC_HEAP = 1,
+	PGSTAT_EXTVAC_INDEX = 2
+} ExtVacReportType;
+
 /* ----------
  *
  * ExtVacReport
  *
- * Additional statistics of vacuum processing over a heap relation.
+ * 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
@@ -203,14 +212,38 @@ typedef struct ExtVacReport
 	/* Interruptions on any errors. */
 	int32		interrupts;
 
-	int64		pages_scanned;		/* number of pages we examined */
-	int64		pages_removed;		/* number of pages removed by vacuum */
-	int64		pages_frozen;		/* number of pages marked in VM as frozen */
-	int64		pages_all_visible;	/* number of pages marked in VM as all-visible */
-	int64		tuples_deleted;		/* tuples deleted by vacuum */
-	int64		tuples_frozen;		/* tuples frozen up by vacuum */
-	int64		dead_tuples;		/* number of deleted tuples which vacuum cannot clean up by vacuum operation */
-	int64		index_vacuum_count;	/* number of index vacuumings */
+	ExtVacReportType type;		/* heap, index, etc. */
+
+	/* ----------
+	 *
+	 * There are separate metrics of statistic for tables and indexes,
+	 * which collect during vacuum.
+	 * The union operator allows to combine these statistics
+	 * so that each metric is assigned to a specific class of collected statistics.
+	 * Such a combined structure was called per_type_stats.
+	 * The name of the structure itself is not used anywhere,
+	 * it exists only for understanding the code.
+	 * ----------
+	*/
+	union
+	{
+		struct
+		{
+			int64		pages_scanned;		/* number of pages we examined */
+			int64		pages_removed;		/* number of pages removed by vacuum */
+			int64		pages_frozen;		/* number of pages marked in VM as frozen */
+			int64		pages_all_visible;	/* number of pages marked in VM as all-visible */
+			int64		tuples_deleted;		/* tuples deleted by vacuum */
+			int64		tuples_frozen;		/* tuples frozen up by vacuum */
+			int64		dead_tuples;		/* number of deleted tuples which vacuum cannot clean up by vacuum operation */
+			int64		index_vacuum_count;	/* number of index vacuumings */
+		}			heap;
+		struct
+		{
+			int64		pages_deleted;		/* number of pages deleted by vacuum */
+			int64		tuples_deleted;		/* tuples deleted by vacuum */
+		}			index;
+	} /* per_type_stats */;
 } ExtVacReport;
 
 /* ----------
@@ -689,7 +722,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);
-extern void pgstat_report_vacuum_error(Oid tableoid);
+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/expected/vacuum-extending-in-repetable-read.out b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
index 7cdb79c0ec4..93fe15c01f9 100644
--- a/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
+++ b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
@@ -9,10 +9,9 @@ step s2_print_vacuum_stats_table:
     FROM pg_stat_vacuum_tables vt, pg_class c
     WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid;
 
-relname                   |tuples_deleted|dead_tuples|tuples_frozen
---------------------------+--------------+-----------+-------------
-test_vacuum_stat_isolation|             0|          0|            0
-(1 row)
+relname|tuples_deleted|dead_tuples|tuples_frozen
+-------+--------------+-----------+-------------
+(0 rows)
 
 step s1_begin_repeatable_read: 
   BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
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 7d31ddbece9..bca3e8516b2 100644
--- a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -48,4 +48,4 @@ permutation
     s1_commit
     s2_checkpoint
     s2_vacuum
-    s2_print_vacuum_stats_table
+    s2_print_vacuum_stats_table
\ No newline at end of file
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 9ae743eae0c..5d72b970b03 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -32,10 +32,11 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
        prokind NOT IN ('f', 'a', 'w', 'p') OR
        provolatile NOT IN ('i', 's', 'v') OR
        proparallel NOT IN ('s', 'r', 'u');
- oid  |        proname        
-------+-----------------------
+ oid  |        proname         
+------+------------------------
  8001 | pg_stat_vacuum_tables
-(1 row)
+ 8002 | pg_stat_vacuum_indexes
+(2 rows)
 
 -- prosrc should never be null; it can be empty only if prosqlbody isn't null
 SELECT p1.oid, p1.proname
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6e8790f66f6..437aa33f16c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2229,6 +2229,32 @@ pg_stat_user_tables| SELECT relid,
     autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
+    ns.nspname AS schema,
+    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.system_time,
+    stats.user_time,
+    stats.total_time,
+    stats.interrupts
+   FROM pg_database db,
+    pg_class rel,
+    pg_namespace ns,
+    LATERAL pg_stat_vacuum_indexes(db.oid, 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, system_time, user_time, total_time, interrupts)
+  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
 pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     rel.relname,
diff --git a/src/test/regress/expected/vacuum_index_statistics.out b/src/test/regress/expected/vacuum_index_statistics.out
new file mode 100644
index 00000000000..a0da8d25f1a
--- /dev/null
+++ b/src/test/regress/expected/vacuum_index_statistics.out
@@ -0,0 +1,158 @@
+--
+-- Test cumulative vacuum stats system
+--
+-- Check the wall statistics collected during vacuum operation:
+-- number of frozen and visible pages set by vacuum;
+-- 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 
+--------------
+ on
+(1 row)
+
+-- 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 
+--------------------------
+ 
+(1 row)
+
+\set sample_size 10000
+SET vacuum_freeze_min_age = 0;
+SET vacuum_freeze_table_age = 0;
+--SET stats_fetch_consistency = snapshot;
+CREATE TABLE vestat (x int primary key) WITH (autovacuum_enabled = off, fillfactor = 10);
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+SELECT oid AS ioid from pg_class where relname = 'vestat_pkey' \gset
+DELETE FROM vestat WHERE x % 2 = 0;
+-- Before the first vacuum execution extended stats view is empty.
+SELECT vt.relname,relpages,pages_deleted,tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+ relname | relpages | pages_deleted | tuples_deleted 
+---------+----------+---------------+----------------
+(0 rows)
+
+SELECT relpages AS irp
+FROM pg_class c
+WHERE relname = 'vestat_pkey' \gset
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- The table and index extended vacuum statistics should show us that
+-- vacuum frozed pages and clean up pages, but pages_removed stayed the same
+-- because of not full table have cleaned up
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted = 0 AS pages_deleted,tuples_deleted > 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | t        | t             | t
+(1 row)
+
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+-- Look into WAL records deltas.
+SELECT wal_records > 0 AS diWR, wal_bytes > 0 AS diWB
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey';
+ diwr | diwb 
+------+------
+ t    | t
+(1 row)
+
+DELETE FROM vestat;;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- pages_removed must be increased
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd > 0 AS pages_deleted,tuples_deleted-:itd > 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | t        | t             | t
+(1 row)
+
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records-:iwr AS diwr, wal_bytes-:iwb AS diwb, wal_fpi-:ifpi AS difpi
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+-- WAL advance should be detected.
+SELECT :diwr > 0 AS diWR, :diwb > 0 AS diWB;
+ diwr | diwb 
+------+------
+ t    | t
+(1 row)
+
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+DELETE FROM vestat WHERE x % 2 = 0;
+-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics
+-- are detected here.
+VACUUM FULL vestat;
+-- It is necessary to check the wal statistics
+CHECKPOINT;
+-- Store WAL advances into variables
+SELECT wal_records-:iwr AS diwr2, wal_bytes-:iwb AS diwb2, wal_fpi-:ifpi AS difpi2
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+-- WAL and other statistics advance should not be detected.
+SELECT :diwr2=0 AS diWR, :difpi2=0 AS iFPI, :diwb2=0 AS diWB;
+ diwr | ifpi | diwb 
+------+------+------
+ t    | t    | t
+(1 row)
+
+SELECT vt.relname,relpages-:irp < 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | t        | t             | t
+(1 row)
+
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+DELETE FROM vestat;
+TRUNCATE vestat;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- Store WAL advances into variables after removing all tuples from the table
+SELECT wal_records-:iwr AS diwr3, wal_bytes-:iwb AS diwb3, wal_fpi-:ifpi AS difpi3
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+--There are nothing changed
+SELECT :diwr3=0 AS diWR, :difpi3=0 AS iFPI, :diwb3=0 AS diWB;
+ diwr | ifpi | diwb 
+------+------+------
+ t    | t    | t
+(1 row)
+
+--
+-- Now, the table and index is compressed into zero number of pages. Check it
+-- in vacuum extended statistics.
+-- The pages_frozen, pages_scanned values shouldn't be changed
+--
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | f        | t             | t
+(1 row)
+
+DROP TABLE vestat;
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_statistics.out
index 1a7d04b0590..b85a5cab9af 100644
--- a/src/test/regress/expected/vacuum_tables_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_statistics.out
@@ -37,8 +37,7 @@ FROM pg_stat_vacuum_tables vt, pg_class c
 WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
  relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
 ---------+--------------+----------------+----------+---------------+---------------
- vestat  |            0 |              0 |      455 |             0 |             0
-(1 row)
+(0 rows)
 
 SELECT relpages AS rp
 FROM pg_class c
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f8a4bcccc9d..b9408a43f71 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -140,4 +140,5 @@ test: tablespace
 # ----------
 # Check vacuum statistics
 # ----------
+test: vacuum_index_statistics
 test: vacuum_tables_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
new file mode 100644
index 00000000000..9113fd26e6f
--- /dev/null
+++ b/src/test/regress/sql/vacuum_index_statistics.sql
@@ -0,0 +1,128 @@
+--
+-- Test cumulative vacuum stats system
+--
+-- Check the wall statistics collected during vacuum operation:
+-- number of frozen and visible pages set by vacuum;
+-- 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
+-- 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();
+
+\set sample_size 10000
+SET vacuum_freeze_min_age = 0;
+SET vacuum_freeze_table_age = 0;
+--SET stats_fetch_consistency = snapshot;
+CREATE TABLE vestat (x int primary key) WITH (autovacuum_enabled = off, fillfactor = 10);
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+
+SELECT oid AS ioid from pg_class where relname = 'vestat_pkey' \gset
+
+DELETE FROM vestat WHERE x % 2 = 0;
+-- Before the first vacuum execution extended stats view is empty.
+SELECT vt.relname,relpages,pages_deleted,tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+SELECT relpages AS irp
+FROM pg_class c
+WHERE relname = 'vestat_pkey' \gset
+
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+
+-- The table and index extended vacuum statistics should show us that
+-- vacuum frozed pages and clean up pages, but pages_removed stayed the same
+-- because of not full table have cleaned up
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted = 0 AS pages_deleted,tuples_deleted > 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+
+-- Look into WAL records deltas.
+SELECT wal_records > 0 AS diWR, wal_bytes > 0 AS diWB
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey';
+
+DELETE FROM vestat;;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+
+-- pages_removed must be increased
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd > 0 AS pages_deleted,tuples_deleted-:itd > 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+
+-- Store WAL advances into variables
+SELECT wal_records-:iwr AS diwr, wal_bytes-:iwb AS diwb, wal_fpi-:ifpi AS difpi
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+
+-- WAL advance should be detected.
+SELECT :diwr > 0 AS diWR, :diwb > 0 AS diWB;
+
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+DELETE FROM vestat WHERE x % 2 = 0;
+-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics
+-- are detected here.
+VACUUM FULL vestat;
+-- It is necessary to check the wal statistics
+CHECKPOINT;
+
+-- Store WAL advances into variables
+SELECT wal_records-:iwr AS diwr2, wal_bytes-:iwb AS diwb2, wal_fpi-:ifpi AS difpi2
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+
+-- WAL and other statistics advance should not be detected.
+SELECT :diwr2=0 AS diWR, :difpi2=0 AS iFPI, :diwb2=0 AS diWB;
+
+SELECT vt.relname,relpages-:irp < 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+
+DELETE FROM vestat;
+TRUNCATE vestat;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+
+-- Store WAL advances into variables after removing all tuples from the table
+SELECT wal_records-:iwr AS diwr3, wal_bytes-:iwb AS diwb3, wal_fpi-:ifpi AS difpi3
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+
+--There are nothing changed
+SELECT :diwr3=0 AS diWR, :difpi3=0 AS iFPI, :diwb3=0 AS diWB;
+
+--
+-- Now, the table and index is compressed into zero number of pages. Check it
+-- in vacuum extended statistics.
+-- The pages_frozen, pages_scanned values shouldn't be changed
+--
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+
+DROP TABLE vestat;
-- 
2.34.1

From 3eb427caa4b630af6d371b62ee7bf24511eff0f3 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 11 Jun 2024 13:55:39 +0300
Subject: [PATCH 3/4] Machinery for grabbing an extended vacuum statistics on
 databases. It transmits vacuum statistical information about each table and
 accumulates it for the database which the table belonged.

---
 src/backend/catalog/system_views.sql          | 28 +++++++
 src/backend/utils/activity/pgstat.c           |  2 +
 src/backend/utils/activity/pgstat_database.c  |  1 +
 src/backend/utils/activity/pgstat_relation.c  | 16 ++++
 src/backend/utils/adt/pgstatfuncs.c           | 79 +++++++++++++++++++
 src/include/catalog/pg_proc.dat               |  9 +++
 src/include/pgstat.h                          |  3 +-
 src/test/regress/expected/opr_sanity.out      |  7 +-
 src/test/regress/expected/rules.out           | 18 +++++
 ...ut => vacuum_tables_and_db_statistics.out} | 78 ++++++++++++++++++
 src/test/regress/parallel_schedule            |  2 +-
 ...ql => vacuum_tables_and_db_statistics.sql} | 66 +++++++++++++++-
 12 files changed, 303 insertions(+), 6 deletions(-)
 rename src/test/regress/expected/{vacuum_tables_statistics.out => vacuum_tables_and_db_statistics.out} (77%)
 rename src/test/regress/sql/{vacuum_tables_statistics.sql => vacuum_tables_and_db_statistics.sql} (79%)

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 05f8fc07108..ca3ad09727e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1466,3 +1466,31 @@ WHERE
   rel.oid = stats.relid AND
   ns.oid = rel.relnamespace;
 
+CREATE VIEW pg_stat_vacuum_database AS
+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.system_time,
+  stats.user_time,
+  stats.total_time,
+
+  stats.interrupts
+FROM
+  pg_database db LEFT JOIN pg_stat_vacuum_database(db.oid) stats
+ON
+  db.oid = stats.dboid;
+
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 75890b18988..3c50bea379c 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -1099,6 +1099,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
 		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
 		if (kind == PGSTAT_KIND_RELATION)
 			pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+		else if (kind == PGSTAT_KIND_DATABASE)
+			pgstat_build_snapshot(PGSTAT_KIND_DATABASE);
 	}
 	PG_FINALLY();
 	{
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 29bc0909748..a060d1a4042 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -430,6 +430,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 5b06b04faad..cc09aba571f 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -217,6 +217,7 @@ pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
 	PgStatShared_Relation *shtabentry;
 	PgStat_StatTabEntry *tabentry;
 	Oid			dboid =  MyDatabaseId;
+	PgStat_StatDBEntry *dbentry;	/* pending database entry */
 
 	if (!pgstat_track_counts)
 		return;
@@ -230,6 +231,10 @@ pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
 	tabentry->vacuum_ext.interrupts++;
 	tabentry->vacuum_ext.type = m_type;
 	pgstat_unlock_entry(entry_ref);
+
+	dbentry = pgstat_prep_database_pending(dboid);
+	dbentry->vacuum_ext.interrupts++;
+	dbentry->vacuum_ext.type = m_type;
 }
 
 /*
@@ -243,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;
 
@@ -296,6 +302,16 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	 * VACUUM command has processed all tables and committed.
 	 */
 	pgstat_flush_io(false);
+	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);
+	}
+
 }
 
 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index b868b917ceb..0c490ba5f1a 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2036,6 +2036,7 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
 #define EXTVACHEAPSTAT_COLUMNS	27
 #define EXTVACIDXSTAT_COLUMNS	19
+#define EXTVACDBSTAT_COLUMNS	15
 #define EXTVACSTAT_COLUMNS Max(EXTVACHEAPSTAT_COLUMNS, EXTVACIDXSTAT_COLUMNS)
 
 static Oid CurrentDatabaseId = InvalidOid;
@@ -2076,6 +2077,48 @@ fetch_dbstat_tabentry(Oid dbid, Oid relid)
 	return tabentry;
 }
 
+static void
+tuplestore_put_for_database(Oid dbid, Tuplestorestate *tupstore,
+			   TupleDesc tupdesc, PgStatShared_Database *dbentry, int ncolumns)
+{
+	Datum		values[EXTVACDBSTAT_COLUMNS];
+	bool		nulls[EXTVACDBSTAT_COLUMNS];
+	char		buf[256];
+	int			i = 0;
+
+	memset(nulls, 0, EXTVACDBSTAT_COLUMNS * sizeof(bool));
+
+	values[i++] = ObjectIdGetDatum(dbid);
+
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_read);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_hit);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_dirtied);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_written);
+
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.wal_records);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.wal_fpi);
+
+	/* Convert to numeric, like pg_stat_statements */
+	snprintf(buf, sizeof buf, UINT64_FORMAT, dbentry->stats.vacuum_ext.wal_bytes);
+	values[i++] = DirectFunctionCall3(numeric_in,
+									  CStringGetDatum(buf),
+									  ObjectIdGetDatum(0),
+									  Int32GetDatum(-1));
+
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.blk_read_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.blk_write_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.delay_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.system_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.user_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.total_time);
+	values[i++] = Int32GetDatum(dbentry->stats.vacuum_ext.interrupts);
+
+
+	Assert(i == ncolumns);
+
+	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+}
+
 static void
 tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
 			   TupleDesc tupdesc, PgStat_StatTabEntry *tabentry, int ncolumns)
@@ -2220,6 +2263,31 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 			}
 		}
 	}
+	else if (type == PGSTAT_EXTVAC_DB)
+	{
+		PgStatShared_Database	   *dbentry;
+		PgStat_EntryRef 		   *entry_ref;
+		Oid						storedMyDatabaseId = MyDatabaseId;
+
+		pgstat_update_snapshot(PGSTAT_KIND_DATABASE);
+		MyDatabaseId = storedMyDatabaseId;
+
+		if (OidIsValid(dbid))
+		{
+			entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
+											dbid, InvalidOid, false);
+			dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
+
+			if (dbentry == NULL)
+				/* Table doesn't exist or isn't a heap relation */
+				PG_RETURN_NULL();
+
+			tuplestore_put_for_database(dbid, tupstore, tupdesc, dbentry, ncolumns);
+			pgstat_unlock_entry(entry_ref);
+		}
+		else
+			PG_RETURN_NULL();
+	}
 	PG_RETURN_NULL();
 }
 
@@ -2244,3 +2312,14 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 
 	PG_RETURN_NULL();
 }
+
+/*
+ * Get the vacuum statistics for the database.
+ */
+Datum
+pg_stat_vacuum_database(PG_FUNCTION_ARGS)
+{
+		return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_DB, EXTVACDBSTAT_COLUMNS);
+
+	PG_RETURN_NULL();
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2e80a2502cc..b2e881aa89d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12272,4 +12272,13 @@
   proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{dboid,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,system_time,user_time,total_time,interrupts}',
   prosrc => 'pg_stat_vacuum_indexes' }
+{ oid => '8003',
+  descr => 'pg_stat_vacuum_database return stats values',
+  proname => 'pg_stat_vacuum_database', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proretset => 't',
+  proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,o,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,system_time,user_time,total_time,interrupts}',
+  prosrc => 'pg_stat_vacuum_database' },
 ]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 762b53b88ed..110e9472f3c 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -173,7 +173,8 @@ typedef enum ExtVacReportType
 {
 	PGSTAT_EXTVAC_INVALID = 0,
 	PGSTAT_EXTVAC_HEAP = 1,
-	PGSTAT_EXTVAC_INDEX = 2
+	PGSTAT_EXTVAC_INDEX = 2,
+	PGSTAT_EXTVAC_DB = 3,
 } ExtVacReportType;
 
 /* ----------
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 5d72b970b03..7026de157e4 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -32,11 +32,12 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
        prokind NOT IN ('f', 'a', 'w', 'p') OR
        provolatile NOT IN ('i', 's', 'v') OR
        proparallel NOT IN ('s', 'r', 'u');
- oid  |        proname         
-------+------------------------
+ oid  |         proname         
+------+-------------------------
  8001 | pg_stat_vacuum_tables
  8002 | pg_stat_vacuum_indexes
-(2 rows)
+ 8003 | pg_stat_vacuum_database
+(3 rows)
 
 -- prosrc should never be null; it can be empty only if prosqlbody isn't null
 SELECT p1.oid, p1.proname
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 437aa33f16c..c4388dd0da1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2229,6 +2229,24 @@ pg_stat_user_tables| SELECT relid,
     autoanalyze_count
    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.system_time,
+    stats.user_time,
+    stats.total_time,
+    stats.interrupts
+   FROM (pg_database db
+     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON ((db.oid = stats.dboid)));
 pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     rel.relname,
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
similarity index 77%
rename from src/test/regress/expected/vacuum_tables_statistics.out
rename to src/test/regress/expected/vacuum_tables_and_db_statistics.out
index b85a5cab9af..f0537aac430 100644
--- a/src/test/regress/expected/vacuum_tables_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -6,6 +6,9 @@
 -- number of frozen and visible pages removed by backend.
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
+CREATE DATABASE statistic_vacuum_database;
+CREATE DATABASE statistic_vacuum_database1;
+\c statistic_vacuum_database;
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
  track_counts 
@@ -196,4 +199,79 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
  t            | t                 | t                    | t
 (1 row)
 
+-- 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,
+       user_time > 0 AS user_time,
+       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 | user_time | total_time 
+---------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
+ statistic_vacuum_database | t           | t                  | t                  | t           | t       | t         | t         | t
+(1 row)
+
+DROP TABLE vestat CASCADE;
+-- 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 statistic_vacuum_database1;
+-- 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,
+       user_time > 0 AS user_time,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = 'statistic_vacuum_database';
+          dbname           | db_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes | user_time | total_time 
+---------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
+ statistic_vacuum_database | t           | t                  | t                  | t           | t       | t         | t         | t
+(1 row)
+
+\c statistic_vacuum_database
+RESET vacuum_freeze_min_age;
+RESET vacuum_freeze_table_age;
 DROP TABLE vestat CASCADE;
+\c statistic_vacuum_database1;
+SELECT count(*)
+FROM pg_database d
+CROSS JOIN pg_stat_vacuum_tables(d.oid, 0)
+WHERE oid = 0; -- must be 0
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*)
+FROM pg_database d
+CROSS JOIN pg_stat_vacuum_database(0)
+WHERE oid = 0; -- must be 0
+ count 
+-------
+     0
+(1 row)
+
+\c postgres
+DROP DATABASE statistic_vacuum_database1;
+DROP DATABASE statistic_vacuum_database;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index b9408a43f71..129b1102028 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -141,4 +141,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_tables_statistics.sql b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
similarity index 79%
rename from src/test/regress/sql/vacuum_tables_statistics.sql
rename to src/test/regress/sql/vacuum_tables_and_db_statistics.sql
index 41e387dd304..43cc8068b0f 100644
--- a/src/test/regress/sql/vacuum_tables_statistics.sql
+++ b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
@@ -7,6 +7,10 @@
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
 
+CREATE DATABASE statistic_vacuum_database;
+CREATE DATABASE statistic_vacuum_database1;
+\c statistic_vacuum_database;
+
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
 -- not enabled by default, but we want to test it...
@@ -155,4 +159,64 @@ VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 SELECT pages_frozen = :pf AS pages_frozen,pages_all_visible = :pv AS pages_all_visible,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 WHERE relname = 'vestat';
 
-DROP TABLE vestat CASCADE;
\ No newline at end of file
+-- 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,
+       user_time > 0 AS user_time,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = current_database();
+
+DROP TABLE vestat CASCADE;
+
+-- 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 statistic_vacuum_database1;
+
+-- 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,
+       user_time > 0 AS user_time,
+       total_time > 0 AS total_time
+FROM
+pg_stat_vacuum_database
+WHERE dbname = 'statistic_vacuum_database';
+
+\c statistic_vacuum_database
+
+RESET vacuum_freeze_min_age;
+RESET vacuum_freeze_table_age;
+DROP TABLE vestat CASCADE;
+
+\c statistic_vacuum_database1;
+SELECT count(*)
+FROM pg_database d
+CROSS JOIN pg_stat_vacuum_tables(d.oid, 0)
+WHERE oid = 0; -- must be 0
+
+SELECT count(*)
+FROM pg_database d
+CROSS JOIN pg_stat_vacuum_database(0)
+WHERE oid = 0; -- must be 0
+
+\c postgres
+DROP DATABASE statistic_vacuum_database1;
+DROP DATABASE statistic_vacuum_database;
-- 
2.34.1

From 7d02ddb8e411b579375ab3466b09862f0d79160a Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Thu, 15 Aug 2024 11:44:47 +0300
Subject: [PATCH 4/4] Add documentation about the system views that are used in
 the machinery of vacuum statistics.

---
 doc/src/sgml/system-views.sgml | 747 +++++++++++++++++++++++++++++++++
 1 file changed, 747 insertions(+)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 634a4c0fab4..42d3ad21486 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -5064,4 +5064,751 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
   </table>
  </sect1>
 
+<sect1 id="view-pg-stats-vacuum-database">
+  <title><structname>pg_stat_vacuum_database</structname></title>
+
+  <indexterm zone="view-pg-stats-vacuum-database">
+   <primary>pg_stat_vacuum_database</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_stat_vacuum_database</structname> will contain
+   one row for each database in the current cluster, showing statistics about
+   vacuuming that database.
+  </para>
+
+  <table>
+   <title><structname>pg_stat_vacuum_database</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>dbid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of a database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_read</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks read by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_hit</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times database blocks were found in the
+        buffer cache by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_dirtied</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks dirtied by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_written</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks written by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_records</structfield> <type>int8</type>
+      </para>
+      <para>
+        Total number of WAL records generated by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_fpi</structfield> <type>int8</type>
+      </para>
+      <para>
+        Total number of WAL full page images generated by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_bytes</structfield> <type>numeric</type>
+      </para>
+      <para>
+        Total amount of WAL bytes generated by vacuum operations
+        performed on this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>blk_read_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Time spent reading database blocks by vacuum operations performed on
+        this database, in milliseconds (if <xref linkend="guc-track-io-timing"/> is enabled,
+        otherwise zero)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>blk_write_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Time spent writing database blocks by vacuum operations performed on
+        this database, in milliseconds (if <xref linkend="guc-track-io-timing"/> is enabled,
+        otherwise zero)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>delay_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Time spent sleeping in a vacuum delay point by vacuum operations performed on
+        this database, in milliseconds (see <xref linkend="runtime-config-resource-vacuum-cost"/>
+        for details)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>system_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        System CPU time of vacuuming this database, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        User CPU time of vacuuming this database, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Total time of vacuuming this database, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interrupts</structfield> <type>int4</type>
+      </para>
+      <para>
+        Number of times vacuum operations performed on this database
+        were interrupted on any errors
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+  <sect1 id="view-pg-stats-vacuum-indexes">
+  <title><structname>pg_stat_vacuum_indexes</structname></title>
+
+  <indexterm zone="view-pg-stats-vacuum-indexes">
+   <primary>pg_stat_vacuum_indexes</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_stat_vacuum_indexes</structname> will contain
+   one row for each index in the current database (including TOAST
+   table indexes), showing statistics about vacuuming that specific index.
+  </para>
+
+  <table>
+   <title><structname>pg_stat_vacuum_indexes</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of an index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>schema</structfield> <type>name</type>
+      </para>
+      <para>
+        Name of the schema this index is in
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relname</structfield> <type>name</type>
+      </para>
+      <para>
+       Name of this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_read</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks read by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_hit</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times database blocks were found in the
+        buffer cache by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_dirtied</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks dirtied by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_written</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks written by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rel_blks_read</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of blocks vacuum operations read from this
+        index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rel_blks_hit</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times blocks of this index were already found
+        in the buffer cache by vacuum operations, so that a read was not necessary
+        (this only includes hits in the
+        &project; buffer cache, not the operating system's file system cache)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pages_deleted</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of pages deleted by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>tuples_deleted</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of dead tuples vacuum operations deleted from this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_records</structfield> <type>int8</type>
+      </para>
+      <para>
+        Total number of WAL records generated by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_fpi</structfield> <type>int8</type>
+      </para>
+      <para>
+        Total number of WAL full page images generated by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_bytes</structfield> <type>numeric</type>
+      </para>
+      <para>
+        Total amount of WAL bytes generated by vacuum operations
+        performed on this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>blk_read_time</structfield> <type>int8</type>
+      </para>
+      <para>
+        Time spent reading database blocks by vacuum operations performed on
+        this index, in milliseconds (if <xref linkend="guc-track-io-timing"/> is enabled,
+        otherwise zero)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>blk_write_time</structfield> <type>int8</type>
+      </para>
+      <para>
+        Time spent writing database blocks by vacuum operations performed on
+        this index, in milliseconds (if <xref linkend="guc-track-io-timing"/> is enabled,
+        otherwise zero)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>delay_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Time spent sleeping in a vacuum delay point by vacuum operations performed on
+        this index, in milliseconds (see <xref linkend="runtime-config-resource-vacuum-cost"/>
+        for details)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>system_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        System CPU time of vacuuming this index, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        User CPU time of vacuuming this index, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Total time of vacuuming this index, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interrupts</structfield> <type>float8</type>
+      </para>
+      <para>
+        Number of times vacuum operations performed on this index
+        were interrupted on any errors
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="view-pg-stats-vacuum-tables">
+  <title><structname>pg_stat_vacuum_tables</structname></title>
+
+  <indexterm zone="view-pg-stats-vacuum-tables">
+   <primary>pg_stat_vacuum_tables</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_stat_vacuum_tables</structname> will contain
+   one row for each table in the current database (including TOAST
+   tables), showing statistics about vacuuming that specific table.
+  </para>
+
+  <table>
+   <title><structname>pg_stat_vacuum_tables</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of a table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>schema</structfield> <type>name</type>
+      </para>
+      <para>
+        Name of the schema this table is in
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relname</structfield> <type>name</type>
+      </para>
+      <para>
+       Name of this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_read</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks read by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_hit</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times database blocks were found in the
+        buffer cache by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_dirtied</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks dirtied by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_blks_written</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of database blocks written by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rel_blks_read</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of blocks vacuum operations read from this
+        table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rel_blks_hit</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times blocks of this table were already found
+        in the buffer cache by vacuum operations, so that a read was not necessary
+        (this only includes hits in the
+        &project; buffer cache, not the operating system's file system cache)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pages_scanned</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of pages examined by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pages_removed</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of pages removed from the physical storage by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pages_frozen</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times vacuum operations marked pages of this table
+        as all-frozen in the visibility map
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pages_all_visible</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times vacuum operations marked pages of this table
+        as all-visible in the visibility map
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>tuples_deleted</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of dead tuples vacuum operations deleted from this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>tuples_frozen</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of tuples of this table that vacuum operations marked as
+        frozen
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>dead_tuples</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of dead tuples vacuum operations left in this table due
+        to their visibility in transactions
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>index_vacuum_count</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times indexes on this table were vacuumed
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rev_all_frozen_pages</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times the all-frozen mark in the visibility map
+        was removed for pages of this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rev_all_visible_pages</structfield> <type>int8</type>
+      </para>
+      <para>
+        Number of times the all-visible mark in the visibility map
+        was removed for pages of this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_records</structfield> <type>int8</type>
+      </para>
+      <para>
+        Total number of WAL records generated by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_fpi</structfield> <type>int8</type>
+      </para>
+      <para>
+        Total number of WAL full page images generated by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>wal_bytes</structfield> <type>numeric</type>
+      </para>
+      <para>
+        Total amount of WAL bytes generated by vacuum operations
+        performed on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>blk_read_time</structfield> <type>int8</type>
+      </para>
+      <para>
+        Time spent reading database blocks by vacuum operations performed on
+        this table, in milliseconds (if <xref linkend="guc-track-io-timing"/> is enabled,
+        otherwise zero)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>blk_write_time</structfield> <type>int8</type>
+      </para>
+      <para>
+        Time spent writing database blocks by vacuum operations performed on
+        this table, in milliseconds (if <xref linkend="guc-track-io-timing"/> is enabled,
+        otherwise zero)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>delay_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Time spent sleeping in a vacuum delay point by vacuum operations performed on
+        this table, in milliseconds (see <xref linkend="runtime-config-resource-vacuum-cost"/>
+        for details)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>system_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        System CPU time of vacuuming this table, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        User CPU time of vacuuming this table, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_time</structfield> <type>float8</type>
+      </para>
+      <para>
+        Total time of vacuuming this table, in milliseconds
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interrupts</structfield> <type>float8</type>
+      </para>
+      <para>
+        Number of times vacuum operations performed on this table
+        were interrupted on any errors
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <para>Columns <structfield>total_*</structfield>, <structfield>wal_*</structfield>
+    and <structfield>blk_*</structfield> include data on vacuuming indexes on this table, while columns
+    <structfield>system_time</structfield> and <structfield>user_time</structfield> only include data
+    on vacuuming the heap.</para>
+ </sect1>
+
 </chapter>
-- 
2.34.1

---

From: Ilia Evdokimov <[email-masked]>
Date: Thu, 15 Aug 2024 12:50:58 +0300

On 15.8.24 11:49, Alena Rybakina wrote:
>>     tuplestore_put_for_relation you can remove 'nulls' array if
>>     you're sure that columns cannot be NULL.
>>

Ah, right! I'm sorry.

> I'm not sure that I fully understand what you mean. Can you explain it 

Ah, I didn't notice that the size of all three tables is different. 
Therefore, it won't be possible to write one function instead of two to 
avoid code duplication. My mistake.

---

From: jian he <[email-masked]>
Date: Fri, 16 Aug 2024 19:12:00 +0800

> Hi!

I've applied all the v5 patches.
0002 and 0003 have white space errors.

+      <para>
+        Number of times blocks of this index were already found
+        in the buffer cache by vacuum operations, so that a read was
not necessary
+        (this only includes hits in the
+        &project; buffer cache, not the operating system's file system cache)
+      </para></entry>

+        Number of times blocks of this table were already found
+        in the buffer cache by vacuum operations, so that a read was
not necessary
+        (this only includes hits in the
+        &project; buffer cache, not the operating system's file system cache)
+      </para></entry>

"&project;"
represents a sgml file placeholder name as "project" and puts all the
content of "project.sgml" to system-views.sgml.
but you don't have "project.sgml". you may check
doc/src/sgml/filelist.sgml or doc/src/sgml/ref/allfiles.sgml
for usage of "&place_holder;".
so you can change it to "project", otherwise doc cannot build.

src/backend/commands/dbcommands.c
we have:
    /*
     * If built with appropriate switch, whine when regression-testing
     * conventions for database names are violated.  But don't complain during
     * initdb.
     */
#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
    if (IsUnderPostmaster && strstr(dbname, "regression") == NULL)
        elog(WARNING, "databases created by regression test cases
should have names including \"regression\"");
#endif
so in src/test/regress/sql/vacuum_tables_and_db_statistics.sql you
need to change dbname:
CREATE DATABASE statistic_vacuum_database;
CREATE DATABASE statistic_vacuum_database1;

+  <para>
TOAST should
<acronym>TOAST</acronym>

+ /* 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");
maybe change to
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("return type must be a row type")));
Later I found out "InitMaterializedSRF(fcinfo, 0);" already did all
the work. much of the code can be gotten rid of.
please check attached.

#define EXTVACHEAPSTAT_COLUMNS    27
#define EXTVACIDXSTAT_COLUMNS    19
#define EXTVACDBSTAT_COLUMNS    15
#define EXTVACSTAT_COLUMNS Max(EXTVACHEAPSTAT_COLUMNS, EXTVACIDXSTAT_COLUMNS)

we already defined MyDatabaseId in src/include/miscadmin.h,
Why do we need "static Oid CurrentDatabaseId = InvalidOid;"?
also src/backend/utils/adt/pgstatfuncs.c already included "miscadmin.h".

the following code one function has 2 return statements?
------------------------------------------------------------------------
/*
 * Get the vacuum statistics for the heap tables.
 */
Datum
pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
{
    return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);

    PG_RETURN_NULL();
}

/*
 * Get the vacuum statistics for the indexes.
 */
Datum
pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
{
    return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);

    PG_RETURN_NULL();
}

/*
 * Get the vacuum statistics for the database.
 */
Datum
pg_stat_vacuum_database(PG_FUNCTION_ARGS)
{
        return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_DB, EXTVACDBSTAT_COLUMNS);

    PG_RETURN_NULL();
}
------------------------------------------------------------------------
in pg_stats_vacuum:
    if (type == PGSTAT_EXTVAC_INDEX || type == PGSTAT_EXTVAC_HEAP)
    {
        Oid                    relid = PG_GETARG_OID(1);

        /* Load table statistics for specified database. */
        if (OidIsValid(relid))
        {
            tabentry = fetch_dbstat_tabentry(dbid, relid);
            if (tabentry == NULL || tabentry->vacuum_ext.type != type)
                /* Table don't exists or isn't an heap relation. */
                PG_RETURN_NULL();

            tuplestore_put_for_relation(relid, tupstore, tupdesc,
tabentry, ncolumns);
        }
        else
        {
         ...
        }
}
I don't understand the ELSE branch. the IF branch means the input
dboid, heap/index oid is correct.
the ELSE branch means table reloid is invalid = 0.
I am not sure 100% what the ELSE Branch means.
Also there are no comments explaining why.
experiments seem to show that  when reloid is 0, it will print out all
the vacuum statistics
for all the tables in the current database. If so, then only super
users can call pg_stats_vacuum?
but the table owner should be able to call pg_stats_vacuum for that
specific table.

/* Type of ExtVacReport */
typedef enum ExtVacReportType
{
    PGSTAT_EXTVAC_INVALID = 0,
    PGSTAT_EXTVAC_HEAP = 1,
    PGSTAT_EXTVAC_INDEX = 2,
    PGSTAT_EXTVAC_DB = 3,
} ExtVacReportType;
generally "HEAP" means table and index, maybe "PGSTAT_EXTVAC_HEAP" would be term

---

From: jian he <[email-masked]>
Date: Mon, 19 Aug 2024 17:32:48 +0800

in pg_stats_vacuum

            tuplestore_put_for_relation(relid, rsinfo, tabentry);
        }
        else
        {
       }

So for functions pg_stat_vacuum_indexes and pg_stat_vacuum_tables,
it seems you didn't check "relid" 's relkind,
you may need to use get_rel_relkind.

---

From: Ilia Evdokimov <[email-masked]>
Date: Mon, 19 Aug 2024 19:28:00 +0300

Are you certain that all tables are included in `pg_stat_vacuum_tables`? 
I'm asking because of the following:

SELECT count(*) FROM pg_stat_all_tables ;
  count
-------
    108
(1 row)

SELECT count(*) FROM pg_stat_vacuum_tables ;
  count
-------
     20
(1 row)

-- 
Regards,
Ilia Evdokimov,
Tantor Labs LCC.

---

From: Alena Rybakina <[email-masked]>
Date: Wed, 21 Aug 2024 01:35:00 +0300

Hi! Thank you very much for your review! Sorry for my late response I 
was overwhelmed by tasks.

> the work. much of the code can be gotten rid of.
I agree with your suggestions for improving the code. I will add this in 
the next version of the patch.
> also src/backend/utils/adt/pgstatfuncs.c already included "miscadmin.h".
Hmm, Tom Lane added "misc admin.h", or I didn't notice something. Could 
you point this out, please?

We used the Current Database Id to output statistics on tables from 
another database, so we need to replace it with a different default 
value. But I want to rewrite this patch to display table statistics only 
for the current database, that is, this part will be removed in the 
future. In my opinion, it would be more correct.
> the following code one function has 2 return statements?
You are right - the second return is superfluous. I'll fix it.
If any reloid has not been set by the user, we output statistics for all 
objects - tables or indexes.In this part of the code, we find all the 
suitable objects from the snapshot, if they belong to the index or table 
type of objects.
> generally "HEAP" means table and index, maybe "PGSTAT_EXTVAC_HEAP" would be term

No, Heap means something like a table in a relationship database, or its 
alternative name is Heap.

---

From: Alena Rybakina <[email-masked]>
Date: Wed, 21 Aug 2024 01:37:16 +0300

We check it there: "tabentry->vacuum_ext.type != type". Or were you 
talking about something else?

---

From: Alena Rybakina <[email-masked]>
Date: Wed, 21 Aug 2024 01:39:15 +0300

I think you've counted the above system tables from the database, but 
I'll double-check it. Thank you for your review!

On 19.08.2024 19:28, Ilia Evdokimov wrote:
> Are you certain that all tables are included in 
> `pg_stat_vacuum_tables`? I'm asking because of the following:
>
>
-- 
Regards,
Alena Rybakina
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

---

From: jian he <[email-masked]>
Date: Thu, 22 Aug 2024 10:47:00 +0800

On Wed, Aug 21, 2024 at 6:37 AM Alena Rybakina
<[email-masked]> wrote:
>
> We check it there: "tabentry->vacuum_ext.type != type". Or were you talking about something else?
>

hi.
I mentioned some points at [1],
Please check the attached patchset to address these issues.

there are four occurrences of "CurrentDatabaseId", i am still confused
with usage of CurrentDatabaseId.

also please don't  top-post, otherwise the archive, like [2] is not
easier to read for future readers.
generally you quote first, then reply.

[1] https://postgr.es/m/CACJufxHb_YGCp=[email-masked]
[2] https://postgr.es/m/[email-masked]

---

From: Kirill Reshke <[email-masked]>
Date: Thu, 22 Aug 2024 09:29:58 +0500

> [2] https://postgr.es/m/[email-masked]

Hi, your points are valid.
Regarding 0003, I also wanted to object database naming in a
regression test during my review but for some reason didn't.Now, as
soon as we already need to change it, I suggest we also change
regression_statistic_vacuum_db1 to something less generic. Maybe
regression_statistic_vacuum_db_unaffected.

-- 
Best regards,
Kirill Reshke

---

From: Alexander Korotkov <[email-masked]>
Date: Fri, 23 Aug 2024 04:07:37 +0300

> > SELECT count(*) FROM pg_stat_vacuum_tables ;

I'd like to do some review a well.

+   MyDatabaseId = dbid;
+
+   PG_TRY();
+   {

I think this is generally wrong to change MyDatabaseId, especially if you
have to wrap it with PG_TRY()/PG_CATCH().  I think, instead we need proper
API changes, i.e. make pgstat_fetch_stat_tabentry() and others take dboid
as an argument.

+/*
+ * Get the vacuum statistics for the heap tables.
+ */
+Datum
+pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
+{
+   return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP,
EXTVACHEAPSTAT_COLUMNS);
+
+   PG_RETURN_NULL();
+}

The PG_RETURN_NULL() is unneeded after another return statement.  However,
does pg_stats_vacuum() need to return anything?  What about making its
return type void?

It would be nice to preserve encapsulation and don't expose pgstat_snapshot
hash in the headers.  I see there is only one usage of it outside of
pgstat.c: pg_stats_vacuum().

This manipulation with storedMyDatabaseId looks pretty useless.  It seems
to be intended to change MyDatabaseId, while I'm not fan of this as I
mentioned above.

It looks scary to reset the whole snapshot each time we access another
database.  Need to also mention that the CurrentDatabaseId machinery isn't
implemented.

New functions
pg_stat_vacuum_tables(), pg_stat_vacuum_indexes(), pg_stat_vacuum_database()
are SRFs.  When zero Oid is passed they report all the objects.  However,
it seems they aren't intended to be used directly.  Instead, there are
views with the same names.  These views always call them with particular
Oids, therefore SRFs always return one row.  Then why bother with SRF?
They could return plain records instead.

Also, as I mentioned above patchset makes a lot of trouble accessing
statistics of relations of another database.  But that seems to be useless
given corresponding views allow to see only relations of the current
database.  Even if you call functions directly, what is the value of this
information given that you don't know the relation oids in another
database?  So, I think if we will give up and limit access to the relations
of the current database patch will become simpler and clearer.

------
Regards,
Alexander Korotkov
Supabase

---

From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 18:59:46 +0300

Hi!

On 23.08.2024 04:07, Alexander Korotkov wrote:
> On Wed, Aug 21, 2024 at 1:39 AM Alena Rybakina 
Thank you very much for your review and contribution to this thread!
> I think this is generally wrong to change MyDatabaseId, especially if 
> you have to wrap it with PG_TRY()/PG_CATCH().  I think, instead we 
> need proper API changes, i.e. make pgstat_fetch_stat_tabentry() and 
> others take dboid as an argument.
I fixed it by deleting this part pf the code. We can display statistics 
only for current database.
> The PG_RETURN_NULL() is unneeded after another return statement.  
> However, does pg_stats_vacuum() need to return anything?  What about 
> making its return type void?
I think you are right, we can not return anything. Fixed.
> It would be nice to preserve encapsulation and don't 
> expose pgstat_snapshot hash in the headers.  I see there is only one 
> usage of it outside of pgstat.c: pg_stats_vacuum().
Fixed.
> This manipulation with storedMyDatabaseId looks pretty useless. It 
> seems to be intended to change MyDatabaseId, while I'm not fan of this 
> as I mentioned above.
Fixed.
> database.  Need to also mention that the CurrentDatabaseId machinery 
> isn't implemented.
Fixed.
> are SRFs.  When zero Oid is passed they report all the objects.  
> However, it seems they aren't intended to be used directly.  Instead, 
> there are views with the same names. These views always call them with 
> particular Oids, therefore SRFs always return one row.  Then why 
> bother with SRF?  They could return plain records instead.

I didn't understand correctly - did you mean that we don't need SRF if 
we need to display statistics for a specific object?

Otherwise, we need this when we display information on all database 
objects (tables or indexes):

while ((entry = ScanStatSnapshot(pgStatLocal.snapshot.stats, &hashiter)) 
!= NULL)
{
     CHECK_FOR_INTERRUPTS();

     tabentry = (PgStat_StatTabEntry *) entry->data;

     if (tabentry != NULL && tabentry->vacuum_ext.type == type)
         tuplestore_put_for_relation(relid, rsinfo, tabentry);
}

I know we can construct a HeapTuple object containing a TupleDesc, 
values, and nulls for a particular object, but I'm not sure we can 
augment it while looping through multiple objects.

/* Initialise attributes information in the tuple descriptor */

  tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_SUBSCRIPTION_STATS_COLS);

...

PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));

If I missed something or misunderstood, can you explain in more detail?

> statistics of relations of another database.  But that seems to be 
> useless given corresponding views allow to see only relations of the 
> current database.  Even if you call functions directly, what is the 
> value of this information given that you don't know the relation oids 
> in another database?  So, I think if we will give up and limit access 
> to the relations of the current database patch will become simpler and 
> clearer.
>
I agree with that and have fixed it already.

-- 
Regards,
Alena Rybakina
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
From 8903b692430e0e999665bc4a41d5fd088749131e Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 10:49:40 +0300
Subject: [PATCH 1/4] Machinery for grabbing an extended vacuum statistics on 
 heap relations.

Authors: Alena Rybakina <[email-masked]>,
	 Andrei Lepikhov <[email-masked]>,
	 Andrei Zubkov <[email-masked]>
Reviewed-by: Dilip Kumar <[email-masked]>, Masahiko Sawada <[email-masked]>,
	     Ilia Evdokimov <[email-masked]>, jian he <[email-masked]>,
	     Kirill Reshke <[email-masked]>, Alexander Korotkov <[email-masked]>
---
 src/backend/access/heap/vacuumlazy.c          | 159 +++++++++++++-
 src/backend/access/heap/visibilitymap.c       |  13 ++
 src/backend/catalog/system_views.sql          |  54 +++++
 src/backend/commands/vacuum.c                 |   4 +
 src/backend/commands/vacuumparallel.c         |   1 +
 src/backend/utils/activity/pgstat.c           |  65 +++++-
 src/backend/utils/activity/pgstat_relation.c  |  35 ++-
 src/backend/utils/adt/pgstatfuncs.c           | 157 ++++++++++++++
 src/backend/utils/error/elog.c                |  13 ++
 src/include/catalog/pg_proc.dat               |  10 +-
 src/include/commands/vacuum.h                 |   1 +
 src/include/pgstat.h                          |  84 +++++++-
 src/include/utils/elog.h                      |   1 +
 src/include/utils/pgstat_internal.h           |   2 +-
 .../vacuum-extending-in-repetable-read.out    |  53 +++++
 src/test/isolation/isolation_schedule         |   1 +
 .../vacuum-extending-in-repetable-read.spec   |  51 +++++
 src/test/regress/expected/opr_sanity.out      |   7 +-
 src/test/regress/expected/rules.out           |  34 +++
 .../expected/vacuum_tables_statistics.out     | 200 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 .../regress/sql/vacuum_tables_statistics.sql  | 158 ++++++++++++++
 22 files changed, 1092 insertions(+), 16 deletions(-)
 create mode 100644 src/test/isolation/expected/vacuum-extending-in-repetable-read.out
 create mode 100644 src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
 create mode 100644 src/test/regress/expected/vacuum_tables_statistics.out
 create mode 100644 src/test/regress/sql/vacuum_tables_statistics.sql

 
+/*

+/* ----------
 
 /*

 

 	if (instrument)

 		if (all_frozen)
 		{
 
 		PageSetAllVisible(page);

 	if (map[mapByte] & mask)
 	{
+		/*
 
 		MarkBufferDirty(vmbuf);
index 19cabc9a47f..e84d6881403 100644
 
+  pg_stat_vacuum_tables(rel.oid) stats

 		VacuumCostBalance = 0;
 
 		/*
index b2ca3f39b7a..4e8d8f8dc77 100644
@@ -190,7 +190,7 @@ static void pgstat_reset_after_failure(void);

 static inline bool pgstat_is_kind_valid(PgStat_Kind kind);
@@ -260,7 +260,6 @@ static bool pgstat_is_initialized = false;
 static bool pgstat_is_shutdown = false;
 #endif
 
-
 /*
  * The different kinds of built-in statistics.
  *
@@ -830,6 +829,40 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 		pgstat_reset_entries_of_kind(kind, ts);
 }
 
+void
 
 /* ------------------------------------------------------------
  * Fetching of stats
@@ -896,7 +929,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)

 	/* if caching is desired, look up in cache */
 	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
@@ -1012,7 +1045,7 @@ pgstat_snapshot_fixed(PgStat_Kind kind)
 		pgstat_clear_snapshot();

@@ -1062,8 +1095,30 @@ pgstat_prep_snapshot(void)
 							   NULL);
 }
 
+
+/*
 
+/* ---------

index 32211371237..1df271286e6 100644
@@ -31,6 +31,42 @@

@@ -2032,3 +2068,124 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
+tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
+							PgStat_StatTabEntry *tabentry)
+	Assert(i == rsinfo->setDesc->natts);
+	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	Oid						relid = PG_GETARG_OID(0);
+	Assert(rsinfo->setDesc->natts == ncolumns);
+	Assert(rsinfo->setResult != NULL);
+		tuplestore_put_for_relation(relid, rsinfo, tabentry);
+				tuplestore_put_for_relation(reloid, rsinfo, tabentry);
+	pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
+	PG_RETURN_VOID();
index 5cbb5b54168..5ead2a8aff8 100644
@@ -1619,6 +1619,19 @@ getinternalerrposition(void)
 
+/*
 
 /*
index 4abc6d95262..2023270f923 100644
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,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_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}',

+/* ----------
+ *
+ * ExtVacReport
+ *

 /* ----------

 

 
 
 /*

 /*
  * Functions in pgstat_replslot.c
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index e54eca5b489..e752c0ce015 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -230,6 +230,7 @@ extern int	geterrlevel(void);
 extern int	geterrposition(void);
 extern int	getinternalerrposition(void);

 /*----------
index fb132e439dc..24ab3ceb717 100644

 /*
 
index 862433ee52b..cc0b5bde0a1 100644
+    LATERAL pg_stat_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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts)

From 35aac7704eabeaefd5ab76bb18c0e68d29388be1 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 17:09:21 +0300
Subject: [PATCH 2/4] Machinery for grabbing an extended vacuum statistics on 
 heap and index relations. Remember, statistic on heap and index relations a 
 bit different (see ExtVacReport to find out more information). The concept of
  the ExtVacReport structure has been complicated to store statistic 
 information for two kinds of relations: for heap and index relations. 
 ExtVacReportType variable helps to determine what the kind is considering 
 now.

---
 src/backend/utils/adt/pgstatfuncs.c           |  99 ++++++-----
 15 files changed, 600 insertions(+), 81 deletions(-)

 
+static void

 	/*
 
 	ivinfo.index = indrel;

 	ivinfo.index = indrel;

 

 
index e84d6881403..ee759cdc5c3 100644
+  pg_stat_vacuum_indexes(rel.oid) stats
index 4e8d8f8dc77..a6f971b5a68 100644
@@ -851,17 +851,33 @@ pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,

 /* ------------------------------------------------------------
@@ -1108,7 +1124,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
 	PG_TRY();
 	{
 		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
-		pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+		if (kind == PGSTAT_KIND_RELATION)
+			pgstat_build_snapshot(PGSTAT_KIND_RELATION);
 	}
 	PG_FINALLY();
 	{
@@ -1163,6 +1180,10 @@ pgstat_build_snapshot(PgStat_Kind statKind)
 		if (p->dropped)
 			continue;

 
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1df271286e6..915c3e59bfa 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2070,17 +2070,19 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 }

 static void
 tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 							PgStat_StatTabEntry *tabentry)
 {
-	Datum		values[EXTVACHEAPSTAT_COLUMNS];
-	bool		nulls[EXTVACHEAPSTAT_COLUMNS];
+	Datum		values[EXTVACSTAT_COLUMNS];
+	bool		nulls[EXTVACSTAT_COLUMNS];
 	char		buf[256];
 	int			i = 0;

 
@@ -2093,16 +2095,25 @@ tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 									tabentry->vacuum_ext.blks_hit);
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_hit);

 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_records);
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_fpi);
@@ -2130,10 +2141,9 @@ tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
  * Get the vacuum statistics for the heap tables or indexes.
  */
 static void
-pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
+pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 {
 	ReturnSetInfo		   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	Oid						relid = PG_GETARG_OID(0);
 	PgStat_StatTabEntry    *tabentry;
 
 	InitMaterializedSRF(fcinfo, 0);
@@ -2146,35 +2156,37 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 	Assert(rsinfo->setDesc->natts == ncolumns);
 	Assert(rsinfo->setResult != NULL);
 
-	/* Load table statistics for specified database. */
-	if (OidIsValid(relid))
+	if (type == PGSTAT_EXTVAC_INDEX || type == PGSTAT_EXTVAC_HEAP)
 	{
-		tabentry = pgstat_fetch_stat_tabentry(relid);
-		if (tabentry == NULL)
-			/* Table don't exists or isn't an heap relation. */
-			return;
+		Oid					relid = PG_GETARG_OID(0);
 
-		tuplestore_put_for_relation(relid, rsinfo, tabentry);
-	}
-	else
-	{
-		SnapshotIterator		hashiter;
-		PgStat_SnapshotEntry   *entry;
-
-		/* Iterate the snapshot */
-		InitSnapshotIterator(pgStatLocal.snapshot.stats, &hashiter);
+		/* Load table statistics for specified relation. */
+		if (OidIsValid(relid))
+		{
+			tabentry = pgstat_fetch_stat_tabentry(relid);
+			if (tabentry == NULL || tabentry->vacuum_ext.type != type)
+				/* Table don't exists or isn't an heap relation. */
+				return;
 
+			tuplestore_put_for_relation(relid, rsinfo, tabentry);

 
-			if (tabentry != NULL)
-				tuplestore_put_for_relation(reloid, rsinfo, tabentry);
+				if (tabentry != NULL && tabentry->vacuum_ext.type == type)
+					tuplestore_put_for_relation(relid, rsinfo, tabentry);
+			}
 		}
 	}
 }
@@ -2185,7 +2197,18 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 Datum
 pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 {
-	pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);
 
 	PG_RETURN_VOID();
 }
+
+/*
+ * Get the vacuum statistics for the indexes.
+ */
+Datum
+pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
+{
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
+
+ 	PG_RETURN_VOID();
+ }
\ No newline at end of file
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2023270f923..604b4f44930 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12263,4 +12263,13 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,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_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}',
   prosrc => 'pg_stat_vacuum_tables' },
+{ oid => '8002',
+  descr => 'pg_stat_vacuum_indexes return stats values',
+  proname => 'pg_stat_vacuum_indexes', 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,float8,float8,int4}',
+  proargmodes => '{i,o,o,o,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,system_time,user_time,total_time,interrupts}',
+  prosrc => 'pg_stat_vacuum_indexes' }
 ]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 4492a0572c6..762b53b88ed 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -167,11 +167,20 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter sync_error_count;
 } PgStat_BackendSubEntry;
 
+

 /* ----------
 
 /*

 
index cc0b5bde0a1..265eb597d69 100644
 
 SELECT relpages AS rp
 FROM pg_class c

From b33b32ec0fa31a2ed4349adb9e087722cd23484b Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 17:42:28 +0300
Subject: [PATCH 3/4] Machinery for grabbing an extended vacuum statistics on
 databases. It transmits vacuum statistical information about each table and
 accumulates it for the database which the table belonged.

---
 src/backend/catalog/system_views.sql          | 28 +++++++
 src/backend/utils/activity/pgstat.c           |  2 +
 src/backend/utils/activity/pgstat_database.c  |  1 +
 src/backend/utils/activity/pgstat_relation.c  | 16 ++++
 src/backend/utils/adt/pgstatfuncs.c           | 75 +++++++++++++++++-
 src/include/catalog/pg_proc.dat               | 11 ++-
 src/include/pgstat.h                          |  3 +-
 src/test/regress/expected/opr_sanity.out      |  7 +-
 src/test/regress/expected/rules.out           | 20 ++++-
 ...ut => vacuum_tables_and_db_statistics.out} | 78 +++++++++++++++++++
 src/test/regress/parallel_schedule            |  2 +-
 ...ql => vacuum_tables_and_db_statistics.sql} | 66 +++++++++++++++-
 12 files changed, 300 insertions(+), 9 deletions(-)
 rename src/test/regress/expected/{vacuum_tables_statistics.out => vacuum_tables_and_db_statistics.out} (76%)
 rename src/test/regress/sql/{vacuum_tables_statistics.sql => vacuum_tables_and_db_statistics.sql} (78%)

index ee759cdc5c3..76a2ffff2bb 100644
 
index a6f971b5a68..b633408777e 100644
@@ -1126,6 +1126,8 @@ pgstat_update_snapshot(PgStat_Kind kind)

 	return true;
 }

 /*

 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 915c3e59bfa..9a9b6f807bf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2071,8 +2071,49 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)

+static void
+tuplestore_put_for_database(Oid dbid, ReturnSetInfo *rsinfo,
+							PgStatShared_Database *dbentry)
+{
+	Datum		values[EXTVACDBSTAT_COLUMNS];
+	bool		nulls[EXTVACDBSTAT_COLUMNS];
+	char		buf[256];
+	int			i = 0;
+
+	memset(nulls, 0, EXTVACDBSTAT_COLUMNS * sizeof(bool));
+
+	values[i++] = ObjectIdGetDatum(dbid);
+
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_read);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_hit);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_dirtied);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_written);
+
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.wal_records);
+	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.wal_fpi);
+
+	/* Convert to numeric, like pg_stat_statements */
+	snprintf(buf, sizeof buf, UINT64_FORMAT, dbentry->stats.vacuum_ext.wal_bytes);
+	values[i++] = DirectFunctionCall3(numeric_in,
+									  CStringGetDatum(buf),
+									  ObjectIdGetDatum(0),
+									  Int32GetDatum(-1));
+
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.blk_read_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.blk_write_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.delay_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.system_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.user_time);
+	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.total_time);
+	values[i++] = Int32GetDatum(dbentry->stats.vacuum_ext.interrupts);
+
+	Assert(i == rsinfo->setDesc->natts);
+	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+}
+
 static void
 tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 							PgStat_StatTabEntry *tabentry)
@@ -2189,6 +2230,26 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 			}
 		}
 	}
+	else if (type == PGSTAT_EXTVAC_DB)
+	{
+		PgStatShared_Database	   *dbentry;
+		PgStat_EntryRef 		   *entry_ref;
+		Oid							dbid = PG_GETARG_OID(0);
+
+		if (OidIsValid(dbid))
+		{
+			entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
+											dbid, InvalidOid, false);
+			dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
+
+			if (dbentry == NULL)
+				/* Table doesn't exist or isn't a heap relation */
+				return;
+
+			tuplestore_put_for_database(dbid, rsinfo, dbentry);
+			pgstat_unlock_entry(entry_ref);
+		}
+	}
 }
 
 /*
@@ -2211,4 +2272,16 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
  	PG_RETURN_VOID();
- }
\ No newline at end of file
+ }
+
+
+/*
+ * Get the vacuum statistics for the database.
+ */
+Datum
+pg_stat_vacuum_database(PG_FUNCTION_ARGS)
+{
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_DB, EXTVACDBSTAT_COLUMNS);
+
+	PG_RETURN_VOID();
+}
\ No newline at end of file
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 604b4f44930..d4696d0c055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12271,5 +12271,14 @@
   proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
   proargmodes => '{i,o,o,o,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,system_time,user_time,total_time,interrupts}',
-  prosrc => 'pg_stat_vacuum_indexes' }
+  prosrc => 'pg_stat_vacuum_indexes' },
+{ oid => '8003',
+  descr => 'pg_stat_vacuum_database return stats values',
+  proname => 'pg_stat_vacuum_database', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proretset => 't',
+  proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,o,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,system_time,user_time,total_time,interrupts}',
+  prosrc => 'pg_stat_vacuum_database' },
 ]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 762b53b88ed..110e9472f3c 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -173,7 +173,8 @@ typedef enum ExtVacReportType
 {
 	PGSTAT_EXTVAC_INVALID = 0,
 	PGSTAT_EXTVAC_HEAP = 1,
-	PGSTAT_EXTVAC_INDEX = 2
+	PGSTAT_EXTVAC_INDEX = 2,
+	PGSTAT_EXTVAC_DB = 3,
 } ExtVacReportType;
 
 /* ----------
 
 -- prosrc should never be null; it can be empty only if prosqlbody isn't null
 SELECT p1.oid, p1.proname
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 265eb597d69..5d7f73c25fd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2229,6 +2229,24 @@ pg_stat_user_tables| SELECT relid,
     autoanalyze_count
    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.system_time,
+    stats.user_time,
+    stats.total_time,
+    stats.interrupts
+   FROM (pg_database db
+     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON ((db.oid = stats.dboid)));
 pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     rel.relname,
@@ -2253,7 +2271,7 @@ pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
    FROM pg_database db,
     pg_class rel,
     pg_namespace ns,
-    LATERAL pg_stat_vacuum_indexes(db.oid, 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, system_time, user_time, total_time, interrupts)
+    LATERAL pg_stat_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, system_time, user_time, total_time, interrupts)
   WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
 pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     ns.nspname AS schema,
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
similarity index 76%
rename from src/test/regress/expected/vacuum_tables_statistics.out
rename to src/test/regress/expected/vacuum_tables_and_db_statistics.out
index b85a5cab9af..ec0cf97e2da 100644
--- a/src/test/regress/expected/vacuum_tables_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -6,6 +6,9 @@
 -- number of frozen and visible pages removed by backend.
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
  track_counts 
@@ -196,4 +199,79 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
  t            | t                 | t                    | t
 (1 row)
 
+--------------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
+ regression_statistic_vacuum_db | t           | t                  | t                  | t           | t       | t         | t         | t
+\c regression_statistic_vacuum_db1;
+WHERE dbname = 'regression_statistic_vacuum_db';
+--------------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
+ regression_statistic_vacuum_db | t           | t                  | t                  | t           | t       | t         | t         | t
+\c regression_statistic_vacuum_db
+\c regression_statistic_vacuum_db1;
+CROSS JOIN pg_stat_vacuum_tables(0)
+DROP DATABASE regression_statistic_vacuum_db1;
+DROP DATABASE regression_statistic_vacuum_db;
similarity index 78%
index 41e387dd304..ed9bb852625 100644
 
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
+
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
 -- not enabled by default, but we want to test it...
@@ -155,4 +159,64 @@ VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 SELECT pages_frozen = :pf AS pages_frozen,pages_all_visible = :pv AS pages_all_visible,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 WHERE relname = 'vestat';
 
+\c regression_statistic_vacuum_db1;
+WHERE dbname = 'regression_statistic_vacuum_db';
+\c regression_statistic_vacuum_db
+\c regression_statistic_vacuum_db1;
+CROSS JOIN pg_stat_vacuum_tables(0)
+DROP DATABASE regression_statistic_vacuum_db1;
+DROP DATABASE regression_statistic_vacuum_db;
-- 
2.34.1

From 0cbb749c24b3c4ee9891c078a4906ee3f24da762 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 17:47:55 +0300
Subject: [PATCH 4/4] Add documentation about the system views that are used in
  the machinery of vacuum statistics.

---

index 634a4c0fab4..8cbccdc4a4d 100644
 
+        project; buffer cache, not the operating system's file system cache)
+        project; buffer cache, not the operating system's file system cache)

---

From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 19:06:51 +0300

> Please check the attached patchset to address these issues.

Thank you for your work! I checked the patches and added your suggested 
changes to the new version of the patch here [0]. In my opinion, nothing 
was missing, but please take a look.

[0] 
https://www.postgresql.org/message-id/c4e4e305-7119-4183-b49a-d7092f4efba3%40postgrespro.ru

> with usage of CurrentDatabaseId.

It needed to be used because of scanning objects from the other 
database, so we change the id of dbid temporary to achieve it.

You should snow that every part of this code was deleted.Now we can 
check information about tables and indexes from the current database.

> [1]https://postgr.es/m/CACJufxHb_YGCp=[email-masked]
> [2]https://postgr.es/m/[email-masked]
Ok, no problem.

---

From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 19:12:40 +0300

On 22.08.2024 07:29, Kirill Reshke wrote:
> On Thu, 22 Aug 2024 at 07:48, jian he<[email-masked]>  wrote:
Hi! I fixed it in the new version of the patch [0]. Feel free to review it!

To be honest, I still doubt that the current database names 
(regression_statistic_vacuum_db and regression_statistic_vacuum_db1) can 
be easily generated, but if you insist on renaming, I will do it.

[0]

---

From: Alena Rybakina <[email-masked]>
Date: Mon, 26 Aug 2024 14:55:13 +0300

Just in case, I have attached a diff file to show the changes for the 
latest version attached here [0] to make the review process easier.

[0] 

-- 
Regards,
Alena Rybakina
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 42d3ad21486..8cbccdc4a4d 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -5360,7 +5360,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
         Number of times blocks of this index were already found
         in the buffer cache by vacuum operations, so that a read was not necessary
         (this only includes hits in the
-        &project; buffer cache, not the operating system's file system cache)
+        project; buffer cache, not the operating system's file system cache)
       </para></entry>
      </row>
 
@@ -5601,7 +5601,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
         Number of times blocks of this table were already found
         in the buffer cache by vacuum operations, so that a read was not necessary
         (this only includes hits in the
-        &project; buffer cache, not the operating system's file system cache)
+        project; buffer cache, not the operating system's file system cache)
       </para></entry>
      </row>
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ca3ad09727e..76a2ffff2bb 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1420,7 +1420,7 @@ FROM
   pg_database db,
   pg_class rel,
   pg_namespace ns,
-  pg_stat_vacuum_tables(db.oid, rel.oid) stats
+  pg_stat_vacuum_tables(rel.oid) stats
 WHERE
   db.datname = current_database() AND
   rel.oid = stats.relid AND
@@ -1460,7 +1460,7 @@ FROM
   pg_database db,
   pg_class rel,
   pg_namespace ns,
-  pg_stat_vacuum_indexes(db.oid, rel.oid) stats
+  pg_stat_vacuum_indexes(rel.oid) stats
 WHERE
   db.datname = current_database() AND
   rel.oid = stats.relid AND
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 3c50bea379c..b633408777e 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -146,6 +146,34 @@
 #define PGSTAT_FILE_ENTRY_HASH	'S' /* stats entry identified by
 									 * PgStat_HashKey */

 /* ----------
  * Local function forward declarations
@@ -232,7 +260,6 @@ static bool pgstat_is_initialized = false;
 static bool pgstat_is_shutdown = false;
 #endif
 
-
 /*
  * The different kinds of built-in statistics.
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0c490ba5f1a..9a9b6f807bf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -33,6 +33,41 @@
 #include "utils/timestamp.h"
 #include "utils/pgstat_internal.h"

 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
@@ -2039,47 +2074,9 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 #define EXTVACDBSTAT_COLUMNS	15
 #define EXTVACSTAT_COLUMNS Max(EXTVACHEAPSTAT_COLUMNS, EXTVACIDXSTAT_COLUMNS)
 
-static Oid CurrentDatabaseId = InvalidOid;
-
-
-/*
- * Fetch stat collector data for specific database and table, which loading from disc.
- * It is maybe expensive, but i guess we won't use that machinery often.
- * The kind of bufferization is based on CurrentDatabaseId value.
- */
-static PgStat_StatTabEntry *
-fetch_dbstat_tabentry(Oid dbid, Oid relid)
-{
-	Oid						storedMyDatabaseId = MyDatabaseId;
-	PgStat_StatTabEntry 	*tabentry = NULL;
-
-	if (OidIsValid(CurrentDatabaseId) && CurrentDatabaseId == dbid)
-		/* Quick path when we read data from the same database */
-		return pgstat_fetch_stat_tabentry(relid);
-
-	pgstat_clear_snapshot();
-
-	/* Tricky turn here: enforce pgstat to think that our database has dbid */
-
-	MyDatabaseId = dbid;
-
-	PG_TRY();
-	{
-		tabentry = pgstat_fetch_stat_tabentry(relid);
-		MyDatabaseId = storedMyDatabaseId;
-	}
-	PG_CATCH();
-	{
-		MyDatabaseId = storedMyDatabaseId;
-	}
-	PG_END_TRY();
-
-	return tabentry;
-}
-
 static void
-tuplestore_put_for_database(Oid dbid, Tuplestorestate *tupstore,
-			   TupleDesc tupdesc, PgStatShared_Database *dbentry, int ncolumns)
+tuplestore_put_for_database(Oid dbid, ReturnSetInfo *rsinfo,
+							PgStatShared_Database *dbentry)
 {
 	Datum		values[EXTVACDBSTAT_COLUMNS];
 	bool		nulls[EXTVACDBSTAT_COLUMNS];
@@ -2113,15 +2110,13 @@ tuplestore_put_for_database(Oid dbid, Tuplestorestate *tupstore,
 	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.total_time);
 	values[i++] = Int32GetDatum(dbentry->stats.vacuum_ext.interrupts);
 
-
-	Assert(i == ncolumns);
-
-	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	Assert(i == rsinfo->setDesc->natts);
+	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 }
 
 static void
-tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
-			   TupleDesc tupdesc, PgStat_StatTabEntry *tabentry, int ncolumns)
+tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
+							PgStat_StatTabEntry *tabentry)
 {
 	Datum		values[EXTVACSTAT_COLUMNS];
 	bool		nulls[EXTVACSTAT_COLUMNS];
@@ -2179,23 +2174,17 @@ tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore,
 	values[i++] = Float8GetDatum(tabentry->vacuum_ext.total_time);
 	values[i++] = Int32GetDatum(tabentry->vacuum_ext.interrupts);
 
-	Assert(i == ncolumns);
-
-	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	Assert(i == rsinfo->setDesc->natts);
+	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 }
 
 /*
  * Get the vacuum statistics for the heap tables or indexes.
  */
-static Datum
+static void
 pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 {
 	ReturnSetInfo		   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	MemoryContext			per_query_ctx;
-	MemoryContext			oldcontext;
-	Tuplestorestate		   *tupstore;
-	TupleDesc				tupdesc;
-	Oid						dbid = PG_GETARG_OID(0);
 	PgStat_StatTabEntry    *tabentry;
 
 	InitMaterializedSRF(fcinfo, 0);
@@ -2205,61 +2194,39 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("set-valued function called in context that cannot accept a set")));
-	/* Switch to long-lived context to create the returned data structures */
-	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
-	oldcontext = MemoryContextSwitchTo(per_query_ctx);
-
-	/* 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");
-
-	Assert(tupdesc->natts == ncolumns);
-
-	tupstore = tuplestore_begin_heap(true, false, work_mem);
-	Assert (tupstore != NULL);
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
+	Assert(rsinfo->setDesc->natts == ncolumns);
+	Assert(rsinfo->setResult != NULL);

 
-			tuplestore_put_for_relation(relid, tupstore, tupdesc, tabentry, ncolumns);
+			tuplestore_put_for_relation(relid, rsinfo, tabentry);
 		}
 		else
 		{
 			SnapshotIterator		hashiter;
 			PgStat_SnapshotEntry   *entry;
-			Oid						storedMyDatabaseId = MyDatabaseId;
-
-			pgstat_update_snapshot(PGSTAT_KIND_RELATION);
-			MyDatabaseId = storedMyDatabaseId;
-
 
 			/* Iterate the snapshot */
 			InitSnapshotIterator(pgStatLocal.snapshot.stats, &hashiter);
 
 			while ((entry = ScanStatSnapshot(pgStatLocal.snapshot.stats, &hashiter)) != NULL)
 			{
-				Oid	reloid;
-
 				CHECK_FOR_INTERRUPTS();

 				if (tabentry != NULL && tabentry->vacuum_ext.type == type)
-					tuplestore_put_for_relation(reloid, tupstore, tupdesc, tabentry, ncolumns);
+					tuplestore_put_for_relation(relid, rsinfo, tabentry);
 			}
 		}
 	}
@@ -2267,10 +2234,7 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 	{
 		PgStatShared_Database	   *dbentry;
 		PgStat_EntryRef 		   *entry_ref;
-		Oid						storedMyDatabaseId = MyDatabaseId;
-
-		pgstat_update_snapshot(PGSTAT_KIND_DATABASE);
-		MyDatabaseId = storedMyDatabaseId;
+		Oid							dbid = PG_GETARG_OID(0);
 
 		if (OidIsValid(dbid))
 		{
@@ -2280,15 +2244,12 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 
 			if (dbentry == NULL)
 				/* Table doesn't exist or isn't a heap relation */
-				PG_RETURN_NULL();
+				return;
 
-			tuplestore_put_for_database(dbid, tupstore, tupdesc, dbentry, ncolumns);
+			tuplestore_put_for_database(dbid, rsinfo, dbentry);
 			pgstat_unlock_entry(entry_ref);
 		}
-		else
-			PG_RETURN_NULL();
 	}
-	PG_RETURN_NULL();
 }
 
 /*
@@ -2297,9 +2258,9 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 Datum
 pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 {
-	return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);
 
-	PG_RETURN_NULL();
+	PG_RETURN_VOID();
 }
 
 /*
@@ -2308,10 +2269,11 @@ pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 Datum
 pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 {
-	return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
+
+ 	PG_RETURN_VOID();
+ }
 
-	PG_RETURN_NULL();
-}
 
 /*
  * Get the vacuum statistics for the database.
@@ -2319,7 +2281,7 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 Datum
 pg_stat_vacuum_database(PG_FUNCTION_ARGS)
 {
-		return pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_DB, EXTVACDBSTAT_COLUMNS);
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_DB, EXTVACDBSTAT_COLUMNS);
 
-	PG_RETURN_NULL();
-}
+	PG_RETURN_VOID();
+}
\ No newline at end of file
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b2e881aa89d..d4696d0c055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12258,20 +12258,20 @@
   descr => 'pg_stat_vacuum_tables return stats values',
   proname => 'pg_stat_vacuum_tables', provolatile => 's', prorettype => 'record',proisstrict => 'f',
   proretset => 't',
-  proargtypes => 'oid oid',
-  proallargtypes => '{oid,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
-  proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{dboid,reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}',
+  proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,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_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}',
   prosrc => 'pg_stat_vacuum_tables' },
 { oid => '8002',
   descr => 'pg_stat_vacuum_indexes return stats values',
   proname => 'pg_stat_vacuum_indexes', provolatile => 's', prorettype => 'record',proisstrict => 'f',
   proretset => 't',
-  proargtypes => 'oid oid',
-  proallargtypes => '{oid,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
-  proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{dboid,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,system_time,user_time,total_time,interrupts}',
-  prosrc => 'pg_stat_vacuum_indexes' }
+  proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
+  proargmodes => '{i,o,o,o,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,system_time,user_time,total_time,interrupts}',
+  prosrc => 'pg_stat_vacuum_indexes' },
 { oid => '8003',
   descr => 'pg_stat_vacuum_database return stats values',
   proname => 'pg_stat_vacuum_database', provolatile => 's', prorettype => 'record',proisstrict => 'f',
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 715ae1b6fd4..24ab3ceb717 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -874,38 +874,4 @@ pgstat_get_custom_snapshot_data(PgStat_Kind kind)
 	return pgStatLocal.snapshot.custom_data[idx];
 }
 
-/* hash table for statistics snapshots entry */
-typedef struct PgStat_SnapshotEntry
-{
-	PgStat_HashKey key;
-	char		status;			/* for simplehash use */
-	void	   *data;			/* the stats data itself */
-} PgStat_SnapshotEntry;
-
-/* ----------
- * Backend-local Hash Table Definitions
- * ----------
- */
-
-/* for stats snapshot entries */
-#define SH_PREFIX pgstat_snapshot
-#define SH_ELEMENT_TYPE PgStat_SnapshotEntry
-#define SH_KEY_TYPE PgStat_HashKey
-#define SH_KEY key
-#define SH_HASH_KEY(tb, key) \
-	pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
-#define SH_EQUAL(tb, a, b) \
-	pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0
-#define SH_SCOPE static inline
-#define SH_DEFINE
-#define SH_DECLARE
-#include "lib/simplehash.h"
-
-typedef pgstat_snapshot_iterator SnapshotIterator;
-
-#define InitSnapshotIterator(htable, iter) \
-	pgstat_snapshot_start_iterate(htable, iter);
-#define ScanStatSnapshot(htable, iter) \
-	pgstat_snapshot_iterate(htable, iter)
-
 #endif							/* PGSTAT_INTERNAL_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c4388dd0da1..5d7f73c25fd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2271,7 +2271,7 @@ pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
    FROM pg_database db,
     pg_class rel,
     pg_namespace ns,
-    LATERAL pg_stat_vacuum_indexes(db.oid, 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, system_time, user_time, total_time, interrupts)
+    LATERAL pg_stat_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, system_time, user_time, total_time, interrupts)
   WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
 pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     ns.nspname AS schema,
@@ -2305,7 +2305,7 @@ pg_stat_vacuum_tables| SELECT rel.oid AS relid,
    FROM pg_database db,
     pg_class rel,
     pg_namespace ns,
-    LATERAL pg_stat_vacuum_tables(db.oid, 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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts)
+    LATERAL pg_stat_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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts)
   WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
diff --git a/src/test/regress/expected/vacuum_tables_and_db_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
index f0537aac430..ec0cf97e2da 100644
--- a/src/test/regress/expected/vacuum_tables_and_db_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -6,9 +6,9 @@
 -- number of frozen and visible pages removed by backend.
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
-CREATE DATABASE statistic_vacuum_database;
-CREATE DATABASE statistic_vacuum_database1;
-\c statistic_vacuum_database;
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
  track_counts 
@@ -212,9 +212,9 @@ SELECT dbname,
 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 | user_time | total_time 
----------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
- statistic_vacuum_database | t           | t                  | t                  | t           | t       | t         | t         | t
+             dbname             | db_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes | user_time | total_time 
+--------------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
+ regression_statistic_vacuum_db | t           | t                  | t                  | t           | t       | t         | t         | t
 (1 row)
 
 DROP TABLE vestat CASCADE;
@@ -230,7 +230,7 @@ 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 statistic_vacuum_database1;
+\c regression_statistic_vacuum_db1;
 -- Now check vacuum statistics for postgres database from another database
 SELECT dbname,
        db_blks_hit > 0 AS db_blks_hit,
@@ -243,20 +243,20 @@ SELECT dbname,
        total_time > 0 AS total_time
 FROM
 pg_stat_vacuum_database
-WHERE dbname = 'statistic_vacuum_database';
-          dbname           | db_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes | user_time | total_time 
----------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
- statistic_vacuum_database | t           | t                  | t                  | t           | t       | t         | t         | t
+WHERE dbname = 'regression_statistic_vacuum_db';
+             dbname             | db_blks_hit | total_blks_dirtied | total_blks_written | wal_records | wal_fpi | wal_bytes | user_time | total_time 
+--------------------------------+-------------+--------------------+--------------------+-------------+---------+-----------+-----------+------------
+ regression_statistic_vacuum_db | t           | t                  | t                  | t           | t       | t         | t         | t
 (1 row)
 
-\c statistic_vacuum_database
+\c regression_statistic_vacuum_db
 RESET vacuum_freeze_min_age;
 RESET vacuum_freeze_table_age;
 DROP TABLE vestat CASCADE;
-\c statistic_vacuum_database1;
+\c regression_statistic_vacuum_db1;
 SELECT count(*)
 FROM pg_database d
-CROSS JOIN pg_stat_vacuum_tables(d.oid, 0)
+CROSS JOIN pg_stat_vacuum_tables(0)
 WHERE oid = 0; -- must be 0
  count 
 -------
@@ -273,5 +273,5 @@ WHERE oid = 0; -- must be 0
 (1 row)
 
 \c postgres
-DROP DATABASE statistic_vacuum_database1;
-DROP DATABASE statistic_vacuum_database;
+DROP DATABASE regression_statistic_vacuum_db1;
+DROP DATABASE regression_statistic_vacuum_db;
diff --git a/src/test/regress/sql/vacuum_tables_and_db_statistics.sql b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
index 43cc8068b0f..ed9bb852625 100644
--- a/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
+++ b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
@@ -7,9 +7,9 @@
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
 
-CREATE DATABASE statistic_vacuum_database;
-CREATE DATABASE statistic_vacuum_database1;
-\c statistic_vacuum_database;
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
 
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
@@ -184,7 +184,7 @@ ANALYZE vestat;
 UPDATE vestat SET x = 10001;
 VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 
-\c statistic_vacuum_database1;
+\c regression_statistic_vacuum_db1;
 
 -- Now check vacuum statistics for postgres database from another database
 SELECT dbname,
@@ -198,18 +198,18 @@ SELECT dbname,
        total_time > 0 AS total_time
 FROM
 pg_stat_vacuum_database
-WHERE dbname = 'statistic_vacuum_database';
+WHERE dbname = 'regression_statistic_vacuum_db';
 
-\c statistic_vacuum_database
+\c regression_statistic_vacuum_db
 
 RESET vacuum_freeze_min_age;
 RESET vacuum_freeze_table_age;
 DROP TABLE vestat CASCADE;
 
-\c statistic_vacuum_database1;
+\c regression_statistic_vacuum_db1;
 SELECT count(*)
 FROM pg_database d
-CROSS JOIN pg_stat_vacuum_tables(d.oid, 0)
+CROSS JOIN pg_stat_vacuum_tables(0)
 WHERE oid = 0; -- must be 0
 
 SELECT count(*)
@@ -218,5 +218,5 @@ CROSS JOIN pg_stat_vacuum_database(0)
 WHERE oid = 0; -- must be 0
 
 \c postgres
-DROP DATABASE statistic_vacuum_database1;
-DROP DATABASE statistic_vacuum_database;
+DROP DATABASE regression_statistic_vacuum_db1;
+DROP DATABASE regression_statistic_vacuum_db;

---

From: Alena Rybakina <[email-masked]>
Date: Wed, 4 Sep 2024 20:23:00 +0300

Hi, all!

I noticed that the pgstat_accumulate_extvac_stats function may be 
declared as static in the pgstat_relation.c file rather than in the 
pgstat.h file.

I fixed part of the code with interrupt counters. I believe that it is 
not worth taking into account the number of interrupts if its level is 
greater than ERROR, for example PANIC. Our server will no longer be 
available to us and statistics data will not help us.

I have attached the new version of the code and the diff files 
(minor-vacuum.no-cbot).

From 1247c8da4964b2426d90195e824e3b8207b8bff3 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Wed, 4 Sep 2024 18:52:40 +0300
Subject: [PATCH 1/3] Machinery for grabbing an extended vacuum statistics on
 heap relations.

 src/backend/utils/activity/pgstat.c           |  32 ++-
 src/backend/utils/activity/pgstat_relation.c  |  72 ++++++-
 src/include/pgstat.h                          |  81 ++++++-
 22 files changed, 1092 insertions(+), 17 deletions(-)

index d82aa3d4896..d63303c7fb7 100644

+/*

+/* ----------
 
 /*

 

 	if (instrument)

 		if (all_frozen)
 		{
 
 		PageSetAllVisible(page);
+			if(geterrelevel() == ERROR)
 
+			if(geterrelevel() == ERROR)
 
 	if (map[mapByte] & mask)
 	{
+		/*
 
 		MarkBufferDirty(vmbuf);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 7fd5d256a18..31be7f04476 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1377,3 +1377,57 @@ CREATE VIEW pg_stat_subscription_stats AS

 
 		VacuumCostBalance = 0;
 
 		/*
index b2ca3f39b7a..808a5f15c82 100644

 
-
 /*
  * The different kinds of built-in statistics.
  *
@@ -830,7 +829,6 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 		pgstat_reset_entries_of_kind(kind, ts);
 }
 
-
 /* ------------------------------------------------------------
  * Fetching of stats
  * ------------------------------------------------------------
@@ -896,7 +894,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)

 	/* if caching is desired, look up in cache */
 	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
@@ -1012,7 +1010,7 @@ pgstat_snapshot_fixed(PgStat_Kind kind)
 		pgstat_clear_snapshot();

@@ -1062,8 +1060,30 @@ pgstat_prep_snapshot(void)
 							   NULL);
 }
 
+
+/*
+ * Trivial external interface to build a snapshot for table statistics only.
+ */
+void
+pgstat_update_snapshot(PgStat_Kind kind)
+{
+	int save_consistency_guc = pgstat_fetch_consistency;
+	pgstat_clear_snapshot();
+
+	PG_TRY();
+	{
+		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
+		pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+	}
+	PG_FINALLY();
+	{
+		pgstat_fetch_consistency = save_consistency_guc;
+	}
+	PG_END_TRY();
+}
+
 static void
-pgstat_build_snapshot(void)
+pgstat_build_snapshot(PgStat_Kind statKind)
 {
 	dshash_seq_status hstat;
 	PgStatShared_HashEntry *p;
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 8a3f7d434cf..791d777fbc6 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -48,6 +48,8 @@ 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);

 /*
@@ -204,12 +206,40 @@ pgstat_drop_relation(Relation rel)
 	}
 }
 
+/* ---------
@@ -233,6 +263,8 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 
@@ -861,6 +893,9 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 
@@ -984,3 +1019,38 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
 		trans->tuples_deleted = trans->deleted_pre_truncdrop;
index 97dc09ac0d9..0966c0bf28b 100644

@@ -2051,3 +2087,124 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)

+/*
 
 /*
index ff5436acacf..8c6dbd4736a 100644
 
 extern PGDLLIMPORT bool VacuumFailsafeActive;
 extern PGDLLIMPORT double vacuum_cost_delay;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index be2c91168a1..8ab80dfe17e 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -169,6 +169,52 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter conflict_count[CONFLICT_NUM_TYPES];
 } PgStat_BackendSubEntry;
 
+/* ----------
+ *
+ * ExtVacReport
+ *
@@ -209,6 +255,16 @@ typedef struct PgStat_TableCounts

 /* ----------
@@ -267,7 +323,7 @@ typedef struct PgStat_TableXactStatus
  * ------------------------------------------------------------
  */

 typedef struct PgStat_ArchiverStats
 {
@@ -386,6 +442,8 @@ typedef struct PgStat_StatDBEntry
 	PgStat_Counter sessions_killed;

@@ -459,6 +517,11 @@ typedef struct PgStat_StatTabEntry
 
 typedef struct PgStat_WalStats
@@ -624,10 +687,12 @@ extern void pgstat_assoc_relation(Relation rel);
 extern void pgstat_unlink_relation(Relation rel);

 /*
@@ -675,6 +740,17 @@ extern void pgstat_report_analyze(Relation rel,
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
 extern void pgstat_count_heap_update(Relation rel, bool hot, bool newpage);
@@ -692,7 +768,6 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared,
 														   Oid reloid);
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 
-
 /*

 /*----------

 /*
index 143109aa4da..e93dd4f626c 100644
 
index a1626f3fae9..10a7a6a6870 100644
@@ -2235,6 +2235,40 @@ pg_stat_user_tables| SELECT relid,
index 7a5a910562e..20640cd72f4 100644

From d737134c8c82c2059577f47eebf1f142efc18e58 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 17:09:21 +0300
Subject: [PATCH 2/3] Machinery for grabbing an extended vacuum statistics on
 heap and index relations. Remember, statistic on heap and index relations a
 bit different (see ExtVacReport to find out more information). The concept of
 the ExtVacReport structure has been complicated to store statistic
 information for two kinds of relations: for heap and index relations.
 ExtVacReportType variable helps to determine what the kind is considering
 now.

---
 src/backend/access/heap/vacuumlazy.c          |  99 +++++++++--
 src/backend/catalog/system_views.sql          |  41 +++++
 src/backend/utils/activity/pgstat.c           |   7 +-
 src/backend/utils/activity/pgstat_relation.c  |  41 +++--
 src/backend/utils/adt/pgstatfuncs.c           |  99 ++++++-----
 src/include/catalog/pg_proc.dat               |   9 +
 src/include/pgstat.h                          |  52 ++++--
 .../vacuum-extending-in-repetable-read.out    |   7 +-
 .../vacuum-extending-in-repetable-read.spec   |   2 +-
 src/test/regress/expected/opr_sanity.out      |   7 +-
 src/test/regress/expected/rules.out           |  26 +++
 .../expected/vacuum_index_statistics.out      | 158 ++++++++++++++++++
 .../expected/vacuum_tables_statistics.out     |   3 +-
 src/test/regress/parallel_schedule            |   1 +
 .../regress/sql/vacuum_index_statistics.sql   | 128 ++++++++++++++
 15 files changed, 599 insertions(+), 81 deletions(-)
 create mode 100644 src/test/regress/expected/vacuum_index_statistics.out
 create mode 100644 src/test/regress/sql/vacuum_index_statistics.sql

index d63303c7fb7..9c53d0b4c57 100644

+static void

 	/*
 
 	ivinfo.index = indrel;

 	ivinfo.index = indrel;

 			if(geterrelevel() == ERROR)
 
 			if(geterrelevel() == ERROR)
 
+			if(geterrelevel() == ERROR)
 
+			if(geterrelevel() == ERROR)
 
+			if(geterrelevel() == ERROR)
index 31be7f04476..bbc8a430712 100644
@@ -1431,3 +1431,44 @@ WHERE
index 808a5f15c82..fb183d5b733 100644
@@ -1073,7 +1073,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
@@ -1128,6 +1129,10 @@ pgstat_build_snapshot(PgStat_Kind statKind)
 		if (p->dropped)
 			continue;

 		stats_data = dsa_get_address(pgStatLocal.dsa, p->body);
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 791d777fbc6..5c95363c04a 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -213,7 +213,7 @@ pgstat_drop_relation(Relation rel)
  * ---------
  */
 void
-pgstat_report_vacuum_error(Oid tableoid)
+pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Relation *shtabentry;
@@ -230,6 +230,7 @@ pgstat_report_vacuum_error(Oid tableoid)
 	tabentry = &shtabentry->stats;

@@ -1042,15 +1043,31 @@ pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,
 	if (!accumulate_reltype_specific_info)
 		return;
 
index 0966c0bf28b..84387507ce7 100644
@@ -2089,17 +2089,19 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 }

 static void

 
@@ -2112,16 +2114,25 @@ tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 									tabentry->vacuum_ext.blks_hit);
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_hit);

@@ -2149,10 +2160,9 @@ tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 
 	InitMaterializedSRF(fcinfo, 0);
@@ -2165,35 +2175,37 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 	Assert(rsinfo->setDesc->natts == ncolumns);
 	Assert(rsinfo->setResult != NULL);

 

 
-			if (tabentry != NULL)
@@ -2204,7 +2216,18 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 
 	PG_RETURN_VOID();
 }
+
+/*
index 8c6dbd4736a..c33419552fe 100644
index 8ab80dfe17e..2e99befe5d0 100644
@@ -169,11 +169,19 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter conflict_count[CONFLICT_NUM_TYPES];
 
@@ -205,14 +213,38 @@ typedef struct ExtVacReport

 /* ----------
@@ -692,7 +724,7 @@ extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 
 /*

 
index 10a7a6a6870..f39a9f6e5a0 100644
@@ -2235,6 +2235,32 @@ pg_stat_user_tables| SELECT relid,
 
 SELECT relpages AS rp
 FROM pg_class c
index 20640cd72f4..25754ff6bd1 100644

From 23a6233f182ea1dea7a9dfa5b3984d17eb7bb3b6 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Sun, 25 Aug 2024 17:42:28 +0300
Subject: [PATCH 3/3] Machinery for grabbing an extended vacuum statistics on
 databases. It transmits vacuum statistical information about each table and
 accumulates it for the database which the table belonged.

---

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index bbc8a430712..f8cee5e79c4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1472,3 +1472,31 @@ WHERE
   rel.oid = stats.relid AND
   ns.oid = rel.relnamespace;
 
index fb183d5b733..583c3ff0f03 100644
@@ -1075,6 +1075,8 @@ pgstat_update_snapshot(PgStat_Kind kind)

 	return true;
 }
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 5c95363c04a..725e26423f2 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -219,6 +219,7 @@ pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
 	PgStatShared_Relation *shtabentry;
 	PgStat_StatTabEntry *tabentry;
 	Oid			dboid =  MyDatabaseId;
+	PgStat_StatDBEntry *dbentry;	/* pending database entry */
 
@@ -232,6 +233,10 @@ pgstat_report_vacuum_error(Oid tableoid, ExtVacReportType m_type)
 
 /*
@@ -245,6 +250,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 
@@ -298,6 +304,16 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 
 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 84387507ce7..11820a5791c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2090,8 +2090,49 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)

+static void
@@ -2208,6 +2249,26 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 
 /*
@@ -2230,4 +2291,16 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
  	PG_RETURN_VOID();
- }
index c33419552fe..b04711bb0a3 100644
index 2e99befe5d0..4c8b0a45331 100644
@@ -174,7 +174,8 @@ typedef enum ExtVacReportType
 
 /* ----------
 
index f39a9f6e5a0..bedcae46fc7 100644
@@ -2235,6 +2235,24 @@ pg_stat_user_tables| SELECT relid,
@@ -2259,7 +2277,7 @@ pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
 
index 25754ff6bd1..301be04a3d6 100644

From 68988e25deb68a944dc3620a13360172e23bca68 Mon Sep 17 00:00:00 2001

---

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 4e2ae78d255..9c53d0b4c57 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -3346,7 +3346,7 @@ vacuum_error_callback(void *arg)
 	switch (errinfo->phase)
 	{
 		case VACUUM_ERRCB_PHASE_SCAN_HEAP:
-			if(geterrelevel() >= ERROR)
+			if(geterrelevel() == ERROR)
 				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
@@ -3363,7 +3363,7 @@ vacuum_error_callback(void *arg)
 			break;
 
 		case VACUUM_ERRCB_PHASE_VACUUM_HEAP:
-			if(geterrelevel() >= ERROR)
+			if(geterrelevel() == ERROR)
 				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			if (BlockNumberIsValid(errinfo->blkno))
 			{
@@ -3380,21 +3380,21 @@ vacuum_error_callback(void *arg)
 			break;
 
 		case VACUUM_ERRCB_PHASE_VACUUM_INDEX:
-			if(geterrelevel() >= ERROR)
+			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)
+			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)
+			if(geterrelevel() == ERROR)
 				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			if (BlockNumberIsValid(errinfo->blkno))
 				errcontext("while truncating relation \"%s.%s\" to %u blocks",
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index b633408777e..583c3ff0f03 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -829,57 +829,6 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 		pgstat_reset_entries_of_kind(kind, ts);
 }
 
-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->system_time += src->system_time;
-	dst->user_time += src->user_time;
-	dst->total_time += src->total_time;
-	dst->interrupts += src->interrupts;
-
-	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_HEAP)
-		{
-			dst->heap.pages_scanned += src->heap.pages_scanned;
-			dst->heap.pages_removed += src->heap.pages_removed;
-			dst->heap.pages_frozen += src->heap.pages_frozen;
-			dst->heap.pages_all_visible += src->heap.pages_all_visible;
-			dst->heap.tuples_deleted += src->heap.tuples_deleted;
-			dst->heap.tuples_frozen += src->heap.tuples_frozen;
-			dst->heap.dead_tuples += src->heap.dead_tuples;
-			dst->heap.index_vacuum_count += src->heap.index_vacuum_count;
-		}
-		else if (dst->type == PGSTAT_EXTVAC_INDEX)
-		{
-			dst->index.pages_deleted += src->index.pages_deleted;
-			dst->index.tuples_deleted += src->index.tuples_deleted;
-		}
-	}
-}
-
 /* ------------------------------------------------------------
  * Fetching of stats
  * ------------------------------------------------------------
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index cc09aba571f..e05de63b2f0 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -48,6 +48,8 @@ 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);

 /*
@@ -1034,3 +1036,66 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
 		trans->tuples_deleted = trans->deleted_pre_truncdrop;
 	}
 }
+
+static void
index c56c54de3b4..eacbee579b3 100644
@@ -646,7 +646,7 @@ extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 
 /*
  * If stats are enabled, but pending data hasn't been prepared yet, call
@@ -721,9 +721,6 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
 extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared,
 														   Oid reloid);
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
-extern void
-pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src,
-							   bool accumulate_reltype_specific_info);
 
 /*

---

From: jian he <[email-masked]>
Date: Thu, 5 Sep 2024 20:47:00 +0800

hi.

still have white space issue when using "git apply",
you may need to use "git diff --check" to find out where.

 /* ----------
diff --git a/src/test/regress/expected/opr_sanity.out
b/src/test/regress/expected/opr_sanity.out

looking at src/test/regress/sql/opr_sanity.sql:

-- **************** pg_proc ****************
-- Look for illegal values in pg_proc fields.

SELECT p1.oid, p1.proname
FROM pg_proc as p1
WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
       p1.pronargs < 0 OR
       p1.pronargdefaults < 0 OR
       p1.pronargdefaults > p1.pronargs OR
       array_lower(p1.proargtypes, 1) != 0 OR
       array_upper(p1.proargtypes, 1) != p1.pronargs-1 OR
       0::oid = ANY (p1.proargtypes) OR
       procost <= 0 OR
       CASE WHEN proretset THEN prorows <= 0 ELSE prorows != 0 END OR
       prokind NOT IN ('f', 'a', 'w', 'p') OR
       provolatile NOT IN ('i', 's', 'v') OR
       proparallel NOT IN ('s', 'r', 'u');

that means
 oid  |         proname
------+-------------------------
 8001 | pg_stat_vacuum_tables
 8002 | pg_stat_vacuum_indexes
 8003 | pg_stat_vacuum_database

These above functions, pg_proc.prorows should > 0 when
pg_proc.proretset is true.
I think that's the opr_sanity test's intention.
so you may need to change pg_proc.dat.

BTW the doc says:
prorows float4, Estimated number of result rows (zero if not proretset)

segmentation fault cases:
select * from pg_stat_vacuum_indexes(0);
select * from pg_stat_vacuum_tables(0);

didn't error out when dbid is invalid?

pg_stat_vacuum_tables
pg_stat_vacuum_indexes
pg_stat_vacuum_database
these functions didn't verify the only input argument oid's kind.
for example:

create table s(a int primary key) with (autovacuum_enabled = off);
create view sv as select * from s;
vacuum s;
select * from pg_stat_vacuum_tables('sv'::regclass::oid);
select * from pg_stat_vacuum_indexes('sv'::regclass::oid);
select * from pg_stat_vacuum_database('sv'::regclass::oid);

above all these 3 examples should error out? because  sv is view.

in src/backend/catalog/system_views.sql
for view creation of pg_stat_vacuum_indexes
you can change to

WHERE
  db.datname = current_database() AND
  rel.oid = stats.relid AND
  ns.oid = rel.relnamespace
AND rel.relkind = 'i':

pg_stat_vacuum_tables  in in src/backend/catalog/system_views.sql
you can change to

WHERE
  db.datname = current_database() AND
  rel.oid = stats.relid AND
  ns.oid = rel.relnamespace
AND rel.relkind = 'r':

---

From: Alena Rybakina <[email-masked]>
Date: Fri, 6 Sep 2024 00:00:27 +0300

Hi! Thank you for your review!

On 05.09.2024 15:47, jian he wrote:
> On Thu, Sep 5, 2024 at 1:23 AM Alena Rybakina<[email-masked]>  wrote:
>> Hi, all!
>>
I agree with you and have fixed it.
> didn't error out when dbid is invalid?
It is caused by the empty statistic snapshot. I have fixed that by 
updating the snapshot (pgstat_update_snapshot(PGSTAT_KIND_RELATION) 
function).

I also added the test to check it.

> above all these 3 examples should error out? because  sv is view.

I don't think so. I noticed that if we try to find the object from the 
system table with the different type the Postgres returns empty rows. I 
think we should do the same.

>    ns.oid = rel.relnamespace
I agree with your proposal and fixed it like that.

From cf3f0f49a625f102c46ef641f84cce9d7afeb655 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Wed, 4 Sep 2024 18:52:40 +0300
Subject: [PATCH 1/3] Machinery for grabbing an extended vacuum statistics on
 heap relations.

 src/backend/catalog/system_views.sql          |  55 +++++
 src/backend/utils/activity/pgstat_relation.c  |  72 +++++-
 src/backend/utils/adt/pgstatfuncs.c           | 156 +++++++++++++
 .../expected/vacuum_tables_statistics.out     | 206 ++++++++++++++++++
 .../regress/sql/vacuum_tables_statistics.sql  | 160 ++++++++++++++
 21 files changed, 1096 insertions(+), 14 deletions(-)

 
+/*

+/* ----------
 
 /*

 

 	if (instrument)

 		if (all_frozen)
 		{
 
 		PageSetAllVisible(page);

 	if (map[mapByte] & mask)
 	{
+		/*
 
 		MarkBufferDirty(vmbuf);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 7fd5d256a18..247147e0213 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1377,3 +1377,58 @@ CREATE VIEW pg_stat_subscription_stats AS
 
+  ns.oid = rel.relnamespace AND
+  rel.relkind = 'r';

 		VacuumCostBalance = 0;
 
 		/*
index 178b5ef65aa..0ae367585fb 100644

 
-
 /*
  * The different kinds of built-in statistics.
  *
@@ -840,7 +839,6 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 		pgstat_reset_entries_of_kind(kind, ts);
 }
 
-
 /* ------------------------------------------------------------
  * Fetching of stats
  * ------------------------------------------------------------
@@ -906,7 +904,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)

 	/* if caching is desired, look up in cache */
 	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
@@ -1022,7 +1020,7 @@ pgstat_snapshot_fixed(PgStat_Kind kind)
 		pgstat_clear_snapshot();

@@ -1072,8 +1070,30 @@ pgstat_prep_snapshot(void)
 							   NULL);
 }
 
+
+/*

 /*
 
+/* ---------

index 97dc09ac0d9..86ba2a68bae 100644

@@ -2051,3 +2087,123 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
+				tuplestore_put_for_relation(entry->key.objoid, rsinfo, tabentry);
 
+/*
 
 /*
index ff5436acacf..d57e181c419 100644
+  proname => 'pg_stat_vacuum_tables', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',

+/* ----------
+ *
+ * ExtVacReport
+ *

 /* ----------

 

 
 
 /*

-
 /*

 /*----------
  * Old-style error reporting API: to be used in this way:
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 25820cbf0a6..a8bba84cb6c 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -555,7 +555,7 @@ extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid,

 /*
index a1626f3fae9..87ce782153b 100644
+  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace) AND (rel.relkind = 'r'::"char"));
index 00000000000..f89e0df79c5
@@ -0,0 +1,206 @@
+SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
+ min 
+ 112
index 00000000000..e2e5a5794c3
@@ -0,0 +1,160 @@
+SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;

From d370a76665bdd5e57a42916ddb234b1ba6be906a Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Thu, 5 Sep 2024 21:00:35 +0300
Subject: [PATCH 2/3] Machinery for grabbing an extended vacuum statistics on
 heap and index relations. Remember, statistic on heap and index relations a
 bit different (see ExtVacReport to find out more information). The concept of
 the ExtVacReport structure has been complicated to store statistic
 information for two kinds of relations: for heap and index relations.
 ExtVacReportType variable helps to determine what the kind is considering
 now.

---
 src/backend/access/heap/vacuumlazy.c          |  99 +++++++++--
 src/backend/catalog/system_views.sql          |  40 +++++
 src/backend/utils/activity/pgstat.c           |   7 +-
 src/backend/utils/activity/pgstat_relation.c  |  41 +++--
 src/backend/utils/adt/pgstatfuncs.c           | 100 +++++++----
 src/include/catalog/pg_proc.dat               |   9 +
 src/include/pgstat.h                          |  52 ++++--
 .../vacuum-extending-in-repetable-read.out    |   7 +-
 .../vacuum-extending-in-repetable-read.spec   |   2 +-
 src/test/regress/expected/rules.out           |  26 +++
 .../expected/vacuum_index_statistics.out      | 164 ++++++++++++++++++
 .../expected/vacuum_tables_statistics.out     |   9 +-
 src/test/regress/parallel_schedule            |   1 +
 .../regress/sql/vacuum_index_statistics.sql   | 130 ++++++++++++++
 14 files changed, 607 insertions(+), 80 deletions(-)
 create mode 100644 src/test/regress/expected/vacuum_index_statistics.out
 create mode 100644 src/test/regress/sql/vacuum_index_statistics.sql

 
+static void

 	/*
 
 	ivinfo.index = indrel;

 	ivinfo.index = indrel;

 

 
 		case VACUUM_ERRCB_PHASE_TRUNCATE:
+			if(geterrelevel() == ERROR)
+				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			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 247147e0213..92f82373c6a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1432,3 +1432,43 @@ WHERE
   rel.oid = stats.relid AND
   ns.oid = rel.relnamespace AND
   rel.relkind = 'r';
+
+CREATE VIEW pg_stat_vacuum_indexes AS
+SELECT
+  rel.oid as relid,
+  ns.nspname AS "schema",
+  rel.relname AS 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.system_time,
+  stats.user_time,
+  stats.total_time,
+  stats.interrupts
+FROM
+  pg_database db,
+  pg_class rel,
+  pg_namespace ns,
+  pg_stat_vacuum_indexes(rel.oid) stats
+WHERE
+  db.datname = current_database() AND
+  rel.oid = stats.relid AND
+  ns.oid = rel.relnamespace AND
+  rel.relkind = 'i';
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 0ae367585fb..1c2f0078880 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -1083,7 +1083,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
 	PG_TRY();
 	{
 		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
-		pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+		if (kind == PGSTAT_KIND_RELATION)
+			pgstat_build_snapshot(PGSTAT_KIND_RELATION);
 	}
 	PG_FINALLY();
 	{
@@ -1138,6 +1139,10 @@ pgstat_build_snapshot(PgStat_Kind statKind)
 		if (p->dropped)
 			continue;

 

index 86ba2a68bae..b08de122674 100644

 static void

 

 
 	InitMaterializedSRF(fcinfo, 0);
@@ -2165,34 +2175,39 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 	Assert(rsinfo->setDesc->natts == ncolumns);
 	Assert(rsinfo->setResult != NULL);

 

 
 
-			if (tabentry != NULL)
-				tuplestore_put_for_relation(entry->key.objoid, rsinfo, tabentry);
+				if (tabentry != NULL && tabentry->vacuum_ext.type == type)
+					tuplestore_put_for_relation(entry->key.objoid, rsinfo, tabentry);
+			}
 		}
 	}
 }
@@ -2203,7 +2218,18 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 Datum
 pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 {
-	pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);
 
 	PG_RETURN_VOID();
 }
+
+/*
index d57e181c419..5fed40d4dc8 100644
+  proname => 'pg_stat_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',

 
 /* ----------
 
 /*

index 87ce782153b..c312428e62b 100644
+  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace) AND (rel.relkind = 'i'::"char"));
index 00000000000..4f6e305710e
@@ -0,0 +1,164 @@
+SELECT min(relid) FROM pg_stat_vacuum_indexes(0);
+ min  
+------
+ 1232
index f89e0df79c5..069ad35056c 100644
 
 SELECT relpages AS rp
 FROM pg_class c
@@ -198,9 +197,9 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 (1 row)
 
 SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
- min 
------
- 112
+ min  
+------
+ 1213
 (1 row)
 
index 00000000000..75e5974eb59
@@ -0,0 +1,130 @@
+SELECT min(relid) FROM pg_stat_vacuum_indexes(0);
+
+DROP TABLE vestat;
-- 
2.34.1

From b70f6a8fc5d1eda1f1dd40339feee99c03a99d25 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Thu, 5 Sep 2024 20:53:05 +0300
Subject: [PATCH 3/3] Machinery for grabbing an extended vacuum statistics on
 databases. It transmits vacuum statistical information about each table and
 accumulates it for the database which the table belonged.

---
 src/backend/utils/adt/pgstatfuncs.c           | 74 +++++++++++++++++-
 11 files changed, 294 insertions(+), 5 deletions(-)

index 92f82373c6a..d5c0ae8b37c 100644
   ns.oid = rel.relnamespace AND
   rel.relkind = 'i';
index 1c2f0078880..fbac089f627 100644
@@ -1085,6 +1085,8 @@ pgstat_update_snapshot(PgStat_Kind kind)

 	return true;
 }

 /*

 /*
index b08de122674..5406102dcbf 100644

+static void
@@ -2210,6 +2251,26 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 
 /*
@@ -2232,4 +2293,15 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
 	PG_RETURN_VOID();
- }
index 5fed40d4dc8..8709a218145 100644
+  proname => 'pg_stat_vacuum_database', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
 
 /* ----------
index c312428e62b..e0dcc513972 100644
index 069ad35056c..fbbb26560df 100644
@@ -202,4 +205,79 @@ SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
  1213
 (1 row)
 
index e2e5a5794c3..3f19936ca61 100644
 
@@ -157,4 +161,64 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 
 SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;

---

diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 11820a5791c..5406102dcbf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2235,6 +2235,8 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 			SnapshotIterator		hashiter;
 			PgStat_SnapshotEntry   *entry;

@@ -2245,7 +2247,7 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 				tabentry = (PgStat_StatTabEntry *) entry->data;
 
 				if (tabentry != NULL && tabentry->vacuum_ext.type == type)
-					tuplestore_put_for_relation(relid, rsinfo, tabentry);
+					tuplestore_put_for_relation(entry->key.objoid, rsinfo, tabentry);
 			}
 		}
 	}
@@ -2290,10 +2292,9 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 {
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
- 	PG_RETURN_VOID();
+	PG_RETURN_VOID();
  }
 
-
 /*
  * Get the vacuum statistics for the database.
  */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b04711bb0a3..8709a218145 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12256,7 +12256,7 @@
   prosrc => 'pg_get_wal_summarizer_state' },
 { oid => '8001',
   descr => 'pg_stat_vacuum_tables return stats values',
-  proname => 'pg_stat_vacuum_tables', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proname => 'pg_stat_vacuum_tables', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
   proretset => 't',
   proargtypes => 'oid',
   proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}',
@@ -12265,7 +12265,7 @@
   prosrc => 'pg_stat_vacuum_tables' },
 { oid => '8002',
   descr => 'pg_stat_vacuum_indexes return stats values',
-  proname => 'pg_stat_vacuum_indexes', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proname => 'pg_stat_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,float8,float8,int4}',
@@ -12274,7 +12274,7 @@
   prosrc => 'pg_stat_vacuum_indexes' },
 { oid => '8003',
   descr => 'pg_stat_vacuum_database return stats values',
-  proname => 'pg_stat_vacuum_database', provolatile => 's', prorettype => 'record',proisstrict => 'f',
+  proname => 'pg_stat_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,float8,float8,int4}',
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 7026de157e4..0d734169f11 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -32,12 +32,9 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
        prokind NOT IN ('f', 'a', 'w', 'p') OR
        provolatile NOT IN ('i', 's', 'v') OR
        proparallel NOT IN ('s', 'r', 'u');
- oid  |         proname         
-------+-------------------------
- 8001 | pg_stat_vacuum_tables
- 8002 | pg_stat_vacuum_indexes
- 8003 | pg_stat_vacuum_database
-(3 rows)
+ oid | proname 
+-----+---------
+(0 rows)
 
 -- prosrc should never be null; it can be empty only if prosqlbody isn't null
 SELECT p1.oid, p1.proname
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index bedcae46fc7..e0dcc513972 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2278,7 +2278,7 @@ pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
     pg_class rel,
     pg_namespace ns,
     LATERAL pg_stat_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, system_time, user_time, total_time, interrupts)
-  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
+  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace) AND (rel.relkind = 'i'::"char"));
 pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     rel.relname,
@@ -2312,7 +2312,7 @@ pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     pg_class rel,
     pg_namespace ns,
     LATERAL pg_stat_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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts)
-  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace));
+  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace) AND (rel.relkind = 'r'::"char"));
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
     wal_bytes,
diff --git a/src/test/regress/expected/vacuum_index_statistics.out b/src/test/regress/expected/vacuum_index_statistics.out
index a0da8d25f1a..4f6e305710e 100644
--- a/src/test/regress/expected/vacuum_index_statistics.out
+++ b/src/test/regress/expected/vacuum_index_statistics.out
@@ -155,4 +155,10 @@ WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
  vestat_pkey | f        | t             | t
 (1 row)
 
+SELECT min(relid) FROM pg_stat_vacuum_indexes(0);
+ min  
+------
+ 1232
+(1 row)
+
 DROP TABLE vestat;
diff --git a/src/test/regress/expected/vacuum_tables_and_db_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
index ec0cf97e2da..fbbb26560df 100644
--- a/src/test/regress/expected/vacuum_tables_and_db_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -199,6 +199,12 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
  t            | t                 | t                    | t
 (1 row)
 
+SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
+ min  
+------
+ 1213
+(1 row)
+
 -- Now check vacuum statistics for current database
 SELECT dbname,
        db_blks_hit > 0 AS db_blks_hit,
diff --git a/src/test/regress/sql/vacuum_index_statistics.sql b/src/test/regress/sql/vacuum_index_statistics.sql
index 9113fd26e6f..75e5974eb59 100644
--- a/src/test/regress/sql/vacuum_index_statistics.sql
+++ b/src/test/regress/sql/vacuum_index_statistics.sql
@@ -125,4 +125,6 @@ SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd = 0 AS pages_
 FROM pg_stat_vacuum_indexes vt, pg_class c
 WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
 
+SELECT min(relid) FROM pg_stat_vacuum_indexes(0);
+
 DROP TABLE vestat;
diff --git a/src/test/regress/sql/vacuum_tables_and_db_statistics.sql b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
index ed9bb852625..3f19936ca61 100644
--- a/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
+++ b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
@@ -159,6 +159,8 @@ VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 SELECT pages_frozen = :pf AS pages_frozen,pages_all_visible = :pv AS pages_all_visible,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 WHERE relname = 'vestat';
 
+SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
+
 -- Now check vacuum statistics for current database
 SELECT dbname,
        db_blks_hit > 0 AS db_blks_hit,

---

From: Masahiko Sawada <[email-masked]>
Date: Fri, 27 Sep 2024 11:15:39 -0700

Hi,

> I have attached the new version of the code and the diff files

Thank you for updating the patches. I've reviewed the 0001 patch and
have two comments.

I think we can split the 0001 patch into two parts: adding
pg_stat_vacuum_tables system views that shows the vacuum statistics
that we are currently collecting such as scanned_pages and
removed_pages, and another one is to add new statistics to collect
such as vacrel->set_all_visible_pages and visibility map updates.

I'm concerned that a pg_stat_vacuum_tables view has some duplicated
statistics that we already collect in different ways. For instance,
total_blks_{read,hit,dirtied,written} are already tracked at
system-level by pg_stat_io, and per-relation block I/O statistics can
be collected using pg_stat_statements. Having duplicated statistics
consumes more memory for pgstat and could confuse users if these
statistics are not consistent. I think it would be better to avoid
collecting duplicated statistics in different places.

Regards,

-- 
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com

---

From: Melanie Plageman <[email-masked]>
Date: Fri, 27 Sep 2024 15:19:31 -0400

> Thank you for updating the patches. I've reviewed the 0001 patch and

I took a very brief look at this and was wondering if it was worth
having a way to make the per-table vacuum statistics opt-in (like a
table storage parameter) in order to decrease the shared memory
footprint of storing the stats.

- Melanie

---

From: Andrei Zubkov <[email-masked]>
Date: Fri, 27 Sep 2024 22:25:14 +0300

Hi,

On Fri, 2024-09-27 at 11:15 -0700, Masahiko Sawada wrote:
> system-level by pg_stat_io,

pg_stat_vacuum_tables.total_blks_{read,hit,dirtied,written} tracks
blocks used by vacuum in different ways while vacuuming this particular
table while pg_stat_io tracks blocks used by vacuum on the cluster
level.

> and per-relation block I/O statistics can
> be collected using pg_stat_statements.

This is impossible. pg_stat_statements tracks block statistics on a 
statement level. One statement could touch many tables and many
indexes, and all used database blocks will be counted by the
pg_stat_statements counters on a statement-level. Autovacuum statistics
won't be accounted by the pg_stat_statements. After all,
pg_stat_statements won't hold the statements statistics forever. Under
pressure of new statements the statement eviction can happen and
statistics will be lost.

All of the above is addressed by relation-level vacuum statistics held
in the Cumulative Statistics System proposed by this patch.
-- 
regards, Andrei Zubkov
Postgres Professional

---

From: Masahiko Sawada <[email-masked]>
Date: Fri, 27 Sep 2024 13:13:15 -0700

> footprint of storing the stats.

I'm not sure how users can select tables that enable vacuum statistics
as I think they basically want to have statistics for all tables, but
I see your point. Since the size of PgStat_TableCounts approximately
tripled by this patch (112 bytes to 320 bytes), it might be worth
considering ways to reduce the number of entries or reducing the size
of vacuum statistics.

Regards,

---

From: Alena Rybakina <[email-masked]>
Date: Sun, 29 Sep 2024 00:22:28 +0300

Hi! Thank you for your interesting for this patch!
> considering ways to reduce the number of entries or reducing the size

The main purpose of these statistics is to see abnormal behavior of 
vacuum in relation to a table or the database as a whole.

For example, there may be a situation where vacuum has started to run 
more often and spends a lot of resources on processing a certain index, 
but the size of the index does not change significantly. Moreover, the 
table in which this index is located can be much smaller in size. This 
may be because the index is bloated and needs to be reindexed.

This is exactly what vacuum statistics can show - we will see that 
compared to other objects, vacuum processed more blocks and spent more 
time on this index.

Perhaps the vacuum parameters for the index should be set more 
aggressively to avoid this in the future.

I suppose that if we turn off statistics collection for a certain 
object, we can miss it. In addition, the user may not enable the 
parameter for the object in time, because he will forget about it.

As for the second option, now I cannot say which statistics can be 
removed, to be honest. So far, they all seem necessary.

-- 
Regards,
Alena Rybakina
Postgres Professional

---

From: Alena Rybakina <[email-masked]>
Date: Tue, 8 Oct 2024 19:18:46 +0300

Made a rebase on a fresh master branch.

-- 
Regards,
Alena Rybakina
Postgres Professional
From e4f31042cf87bf6a22df87f795901506a1c21192 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 8 Oct 2024 18:32:54 +0300
Subject: [PATCH 1/4] Machinery for grabbing an extended vacuum statistics on
 heap relations.

 src/backend/access/heap/vacuumlazy.c          | 159 ++++++++++++-
 .../expected/vacuum_tables_statistics.out     | 209 ++++++++++++++++++
 21 files changed, 1099 insertions(+), 13 deletions(-)

 
+/*

+/* ----------
 
 /*

 

 	if (instrument)

 		if (all_frozen)
 		{
 
 		PageSetAllVisible(page);

 	if (map[mapByte] & mask)
 	{
+		/*
 
 		MarkBufferDirty(vmbuf);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3456b821bc5..ec997531326 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1379,3 +1379,58 @@ CREATE VIEW pg_stat_subscription_stats AS
 
index ac8f5d9c259..36941992b02 100644
 
@@ -2419,6 +2422,7 @@ vacuum_delay_point(void)
 			exit(1);
 
 		VacuumCostBalance = 0;
 
 		/*
index 4fd6574e129..7f7c7c16e23 100644
@@ -1048,6 +1048,7 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
index d1768a89f6e..c283e442f6f 100644

 
-
 /*
  * The different kinds of built-in statistics.
  *
@@ -879,7 +878,6 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 		pgstat_reset_entries_of_kind(kind, ts);
 }
 
-
 /* ------------------------------------------------------------
  * Fetching of stats
  * ------------------------------------------------------------
@@ -945,7 +943,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)

 	/* if caching is desired, look up in cache */
 	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
@@ -1061,7 +1059,7 @@ pgstat_snapshot_fixed(PgStat_Kind kind)
 		pgstat_clear_snapshot();

@@ -1111,8 +1109,30 @@ pgstat_prep_snapshot(void)
 							   NULL);
 }
 
+
+/*

 /*
 
+/* ---------

index f7b50e0b5af..eba1783e51a 100644

@@ -2063,3 +2099,123 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objid));
 }
+
+				tuplestore_put_for_relation(entry->key.objid, rsinfo, tabentry);
index 987ff98067b..ade2f154a71 100644
 
+/*
 
 /*
  * Functions to allow construction of error message strings separately from
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77f54a79e6a..c861e5691cb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12329,4 +12329,13 @@
   proargtypes => 'int2',
   prosrc => 'gist_stratnum_identity' },
 
+{ oid => '8001',
 
index df53fa2d4f9..e764a8c5326 100644
 
+/* ----------
+ *
+ * ExtVacReport
+ *

 /* ----------
 
-#define PGSTAT_FILE_FORMAT_ID	0x01A5BCAF
+#define PGSTAT_FILE_FORMAT_ID	0x01A5BCB1
 
 typedef struct PgStat_ArchiverStats
 {
@@ -388,6 +444,8 @@ typedef struct PgStat_StatDBEntry
 	PgStat_Counter sessions_killed;

@@ -461,6 +519,11 @@ typedef struct PgStat_StatTabEntry
 
 typedef struct PgStat_WalStats
@@ -626,10 +689,12 @@ extern void pgstat_assoc_relation(Relation rel);
 extern void pgstat_unlink_relation(Relation rel);

 /*
@@ -677,6 +742,17 @@ extern void pgstat_report_analyze(Relation rel,
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
 extern void pgstat_count_heap_update(Relation rel, bool hot, bool newpage);
@@ -694,7 +770,6 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared,
 														   Oid reloid);
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 
-
 /*

 /*----------
  * Old-style error reporting API: to be used in this way:
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 61b2e1f96b2..2c0e55d63f3 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -573,7 +573,7 @@ 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);
 
-
index 00000000000..5facb2c862c
+step s2_update                  { UPDATE test_vacuum_stat_isolation SET ival = ival  2 where id > 900; }
index 2b47013f113..700a4863964 100644
@@ -2237,6 +2237,40 @@ pg_stat_user_tables| SELECT relid,
index 00000000000..064064e94b2
@@ -0,0 +1,209 @@
+UPDATE vestat SET x = x1001;
+ERROR:  column "x1001" does not exist
+LINE 1: UPDATE vestat SET x = x1001;
+                              ^
index 4f38104ba01..32c706d3363 100644
index 00000000000..bc8d051aefa
+UPDATE vestat SET x = x1001;

From dd2b403333a87e5754e0c2fb8d133b003fb56746 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 8 Oct 2024 19:11:18 +0300
Subject: [PATCH 2/4] Machinery for grabbing an extended vacuum statistics on 
 heap and index relations. Remember, statistic on heap and index relations a 
 bit different (see ExtVacReport to find out more information). The concept of
  the ExtVacReport structure has been complicated to store statistic 
 information for two kinds of relations: for heap and index relations. 
 ExtVacReportType variable helps to determine what the kind is considering 
 now.

---
 src/backend/utils/adt/pgstatfuncs.c           |  98 +++++++----
 13 files changed, 605 insertions(+), 78 deletions(-)

 
+static void

 	/*
 
 	ivinfo.index = indrel;

 	ivinfo.index = indrel;

 

 
index ec997531326..f5e4e1fbaa5 100644
@@ -1434,3 +1434,43 @@ WHERE
index c283e442f6f..843617eba25 100644
@@ -1122,7 +1122,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
@@ -1177,6 +1178,10 @@ pgstat_build_snapshot(PgStat_Kind statKind)
 		if (p->dropped)
 			continue;

 

index eba1783e51a..e698d637860 100644
@@ -2101,17 +2101,19 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 }

 static void

 
@@ -2124,16 +2126,25 @@ tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 									tabentry->vacuum_ext.blks_hit);
 	values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_hit);

@@ -2161,10 +2172,9 @@ tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 
 	InitMaterializedSRF(fcinfo, 0);
@@ -2177,34 +2187,39 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 	Assert(rsinfo->setDesc->natts == ncolumns);
 	Assert(rsinfo->setResult != NULL);

 

 
 
-			if (tabentry != NULL)
-				tuplestore_put_for_relation(entry->key.objid, rsinfo, tabentry);
+				if (tabentry != NULL && tabentry->vacuum_ext.type == type)
+					tuplestore_put_for_relation(entry->key.objoid, rsinfo, tabentry);
+			}
 		}
 	}
 }
@@ -2217,5 +2232,16 @@ pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 {
 	pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
 
+	PG_RETURN_VOID();
+}
+
+/*
index c861e5691cb..9723551a73a 100644
@@ -12338,4 +12338,13 @@
index e764a8c5326..b784bcc3efe 100644

 
 /* ----------
@@ -694,7 +726,7 @@ extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 
 /*

index 700a4863964..e3290da748d 100644
@@ -2237,6 +2237,32 @@ pg_stat_user_tables| SELECT relid,
index 064064e94b2..86272217e5d 100644
 
 SELECT relpages AS rp
 FROM pg_class c
@@ -201,9 +200,9 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 (1 row)

index 32c706d3363..34fd3e4e674 100644

From 9b2b6ab2ae56d975f823db830c43a4520771662d Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 8 Oct 2024 19:13:05 +0300
Subject: [PATCH 3/4] Machinery for grabbing an extended vacuum statistics on 
 databases. It transmits vacuum statistical information about each table and 
 accumulates it for the database which the table belonged.

---
 src/backend/utils/adt/pgstatfuncs.c           | 76 +++++++++++++++++-
 11 files changed, 295 insertions(+), 6 deletions(-)

index f5e4e1fbaa5..b63c1804b41 100644
@@ -1474,3 +1474,31 @@ WHERE
index 843617eba25..21b29804620 100644
@@ -1124,6 +1124,8 @@ pgstat_update_snapshot(PgStat_Kind kind)

 	return true;
 }

 /*

 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index e698d637860..4d1c099b37e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2102,8 +2102,49 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)

+static void
@@ -2218,10 +2259,30 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 
+					tuplestore_put_for_relation(entry->key.objid, rsinfo, tabentry);
 
 /*
@@ -2230,7 +2291,7 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 Datum
 pg_stat_vacuum_tables(PG_FUNCTION_ARGS)
 {
-	pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS);
+	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_HEAP, EXTVACHEAPSTAT_COLUMNS);
 
 	PG_RETURN_VOID();
 }
@@ -2243,5 +2304,16 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 {
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
+	PG_RETURN_VOID();
+}
+
+/*
index 9723551a73a..936713fe5c1 100644
@@ -12346,5 +12346,14 @@
index b784bcc3efe..c6d663c1c48 100644
 
 /* ----------
index e3290da748d..8359cf3e984 100644
@@ -2237,6 +2237,24 @@ pg_stat_user_tables| SELECT relid,
index 86272217e5d..94dd3214349 100644
@@ -205,4 +208,79 @@ SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
  1213
 (1 row)
 
index 34fd3e4e674..e999232d429 100644
index bc8d051aefa..af1281b3b63 100644

 

From 55a6e8c7dcd9cb3ec9c4e87a13ee9c5bd57183bf Mon Sep 17 00:00:00 2001

---

index 61d28e701f2..93fe9fe36c7 100644

---

From: Ilia Evdokimov <[email-masked]>
Date: Wed, 16 Oct 2024 13:31:43 +0300

> Made a rebase on a fresh master branch.

Thank you for rebasing.

I have noticed that when I create a table or an index on this table, 
there is no information about the table or index in 
pg_stat_vacuum_tables and pg_stat_vacuum_indexes until we perform a VACUUM.

Example:

CREATE TABLE t (i INT, j INT);
INSERT INTO t SELECT i/10, i/100 FROM  GENERATE_SERIES(1,1000000) i;
SELECT * FROM pg_stat_vacuum_tables WHERE relname = 't';
....
(0 rows)
CREATE INDEX ON t (i);
SELECT * FROM pg_stat_vacuum_indexes WHERE relname = 't_i_idx';
...
(0 rows)

I can see the entries after running VACUUM or executing autovacuum. or 
when autovacuum is executed. I would suggest adding a line about the 
relation even if it has not yet been processed by vacuum. Interestingly, 
this issue does not occur with pg_stat_vacuum_database:

CREATE DATABASE example_db;
SELECT * FROM pg_stat_vacuum_database WHERE dbname = 'example_db';
dboid |       dbname | ...
  ...      | example_db | ...
(1 row)

BTW, I recommend renaming the view pg_stat_vacuum_database to 
pg_stat_vacuum_database_S_  for consistency with pg_stat_vacuum_tables 
and pg_stat_vacuum_indexes

--
Regards,
Ilia Evdokimov,
Tantor Labs LLC.

---

From: Alena Rybakina <[email-masked]>
Date: Wed, 16 Oct 2024 14:01:55 +0300

Hi!

On 16.10.2024 13:31, Ilia Evdokimov wrote:
> relation even if it has not yet been processed by 
> vacuum. Interestingly, this issue does not occur with 
> pg_stat_vacuum_database:
>
Thanks for the review. I'm investigating this. I agree with the 
renaming, I will do it in the next version of the patch.

-- 
Regards,
Alena Rybakina
Postgres Professional

---

From: Andrei Zubkov <[email-masked]>
Date: Wed, 16 Oct 2024 14:17:38 +0300

Hi Ilia,

On Wed, 2024-10-16 at 13:31 +0300, Ilia Evdokimov wrote:
> pg_stat_vacuum_databaseS  for consistency with pg_stat_vacuum_tables

Such renaming doesn't seems correct to me because
pg_stat_vacuum_database is consistent with pg_stat_database view, while
pg_stat_vacuum_tables is consistent with pg_stat_all_tables.

This inconsistency is in Postgres views, so it should be changed
synchronously.
-- 
regards, Andrei Zubkov

---

From: Alena Rybakina <[email-masked]>
Date: Tue, 22 Oct 2024 22:30:10 +0300

Hi!

On 16.10.2024 14:01, Alena Rybakina wrote:
>> I can see the entries after running VACUUM or executing 
>> autovacuum. or when autovacuum is executed. I would suggest adding a 
>> line about the relation even if it has not yet been processed by 
>> pg_stat_vacuum_database_S_  for consistency with 
>> pg_stat_vacuum_tables and pg_stat_vacuum_indexes
>>
I fixed it. I added the left outer join to the vacuum views and for 
converting the coalesce function from NULL to null values.

I also fixed the code in getting database statistics - we can get it 
through the existing pgstat_fetch_stat_dbentry function and fixed couple 
of comments.

I attached a diff file, as well as new versions of patches.

-- 
Regards,
Alena Rybakina
Postgres Professional
From 748e194816f6292a6ff5fcb7d8e739a505a03719 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 8 Oct 2024 18:32:54 +0300
Subject: [PATCH 1/3] Machinery for grabbing an extended vacuum statistics on
 heap relations.

 src/backend/catalog/system_views.sql          |  50 +++++
 src/test/regress/expected/rules.out           |  33 +++
 21 files changed, 1093 insertions(+), 13 deletions(-)

 
+/*

+/* ----------
 
 /*

 

 	if (instrument)

 		if (all_frozen)
 		{
 
 		PageSetAllVisible(page);

 	if (map[mapByte] & mask)
 	{
+		/*
 
 		MarkBufferDirty(vmbuf);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3456b821bc5..4fa9886f409 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1379,3 +1379,53 @@ CREATE VIEW pg_stat_subscription_stats AS
 
 CREATE VIEW pg_wait_events AS
     SELECT * FROM pg_get_wait_events();
+--
+-- Show extended cumulative statistics on a vacuum operation over all tables and
+-- databases of the instance.
+-- Use Invalid Oid "0" as an input relation id to get stat on each table in a
+-- database.
+--
+
+CREATE VIEW pg_stat_vacuum_tables AS
+SELECT
+  rel.oid as relid,
+  ns.nspname AS "schema",
+  rel.relname AS relname,
+
+  COALESCE(stats.total_blks_read, 0) AS total_blks_read,
+  COALESCE(stats.total_blks_hit, 0) AS total_blks_hit,
+  COALESCE(stats.total_blks_dirtied, 0) AS total_blks_dirtied,
+  COALESCE(stats.total_blks_written, 0) AS total_blks_written,
+
+  COALESCE(stats.rel_blks_read, 0) AS rel_blks_read,
+  COALESCE(stats.rel_blks_hit, 0) AS rel_blks_hit,
+
+  COALESCE(stats.pages_scanned, 0) AS pages_scanned,
+  COALESCE(stats.pages_removed, 0) AS pages_removed,
+  COALESCE(stats.pages_frozen, 0) AS pages_frozen,
+  COALESCE(stats.pages_all_visible, 0) AS pages_all_visible,
+  COALESCE(stats.tuples_deleted, 0) AS tuples_deleted,
+  COALESCE(stats.tuples_frozen, 0) AS tuples_frozen,
+  COALESCE(stats.dead_tuples, 0) AS dead_tuples,
+
+  COALESCE(stats.index_vacuum_count, 0) AS index_vacuum_count,
+  COALESCE(stats.rev_all_frozen_pages, 0) AS rev_all_frozen_pages,
+  COALESCE(stats.rev_all_visible_pages, 0) AS rev_all_visible_pages,
+
+  COALESCE(stats.wal_records, 0) AS wal_records,
+  COALESCE(stats.wal_fpi, 0) AS wal_fpi,
+  COALESCE(stats.wal_bytes, 0) AS wal_bytes,
+
+  COALESCE(stats.blk_read_time, 0) AS blk_read_time,
+  COALESCE(stats.blk_write_time, 0) AS blk_write_time,
+
+  COALESCE(stats.delay_time, 0) AS delay_time,
+  COALESCE(stats.system_time, 0) AS system_time,
+  COALESCE(stats.user_time, 0) AS user_time,
+  COALESCE(stats.total_time, 0) AS total_time,
+  COALESCE(stats.interrupts, 0) AS interrupts
+
+FROM pg_class rel
+  JOIN pg_namespace ns ON ns.oid = rel.relnamespace
+  LEFT JOIN pg_stat_vacuum_tables(rel.oid) stats ON true
+WHERE rel.relkind = 'r';
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ac8f5d9c259..36941992b02 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -103,6 +103,9 @@ pg_atomic_uint32 *VacuumSharedCostBalance = NULL;
 pg_atomic_uint32 *VacuumActiveNWorkers = NULL;
 int			VacuumCostBalanceLocal = 0;

 		VacuumCostBalance = 0;
 
 		/*

 
-
 /*
 
-

 

+
+/*

 /*
 
+/* ---------

 

index 8acca3e0a0b..fe554547f5b 100644
 
+/*
 
 /*
index 7c0b74fe055..776a7344285 100644
@@ -12363,4 +12363,13 @@
 
+{ oid => '8001',

+/* ----------
+ *
+ * ExtVacReport
+ *

 /* ----------

 

 
 
 /*

-
 /*

 /*----------
 
-
index 2b47013f113..61df9cfc64c 100644
@@ -2237,6 +2237,39 @@ pg_stat_user_tables| SELECT relid,
+    COALESCE(stats.total_blks_read, (0)::bigint) AS total_blks_read,
+    COALESCE(stats.total_blks_hit, (0)::bigint) AS total_blks_hit,
+    COALESCE(stats.total_blks_dirtied, (0)::bigint) AS total_blks_dirtied,
+    COALESCE(stats.total_blks_written, (0)::bigint) AS total_blks_written,
+    COALESCE(stats.rel_blks_read, (0)::bigint) AS rel_blks_read,
+    COALESCE(stats.rel_blks_hit, (0)::bigint) AS rel_blks_hit,
+    COALESCE(stats.pages_scanned, (0)::bigint) AS pages_scanned,
+    COALESCE(stats.pages_removed, (0)::bigint) AS pages_removed,
+    COALESCE(stats.pages_frozen, (0)::bigint) AS pages_frozen,
+    COALESCE(stats.pages_all_visible, (0)::bigint) AS pages_all_visible,
+    COALESCE(stats.tuples_deleted, (0)::bigint) AS tuples_deleted,
+    COALESCE(stats.tuples_frozen, (0)::bigint) AS tuples_frozen,
+    COALESCE(stats.dead_tuples, (0)::bigint) AS dead_tuples,
+    COALESCE(stats.index_vacuum_count, (0)::bigint) AS index_vacuum_count,
+    COALESCE(stats.rev_all_frozen_pages, (0)::bigint) AS rev_all_frozen_pages,
+    COALESCE(stats.rev_all_visible_pages, (0)::bigint) AS rev_all_visible_pages,
+    COALESCE(stats.wal_records, (0)::bigint) AS wal_records,
+    COALESCE(stats.wal_fpi, (0)::bigint) AS wal_fpi,
+    COALESCE(stats.wal_bytes, (0)::numeric) AS wal_bytes,
+    COALESCE(stats.blk_read_time, (0)::double precision) AS blk_read_time,
+    COALESCE(stats.blk_write_time, (0)::double precision) AS blk_write_time,
+    COALESCE(stats.delay_time, (0)::double precision) AS delay_time,
+    COALESCE(stats.system_time, (0)::double precision) AS system_time,
+    COALESCE(stats.user_time, (0)::double precision) AS user_time,
+    COALESCE(stats.total_time, (0)::double precision) AS total_time,
+    COALESCE(stats.interrupts, 0) AS interrupts
+   FROM ((pg_class rel
+     JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace)))
+     LEFT JOIN LATERAL pg_stat_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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts) ON (true))
+  WHERE (rel.relkind = 'r'::"char");
index 81e4222d26a..977a0472027 100644

From 92c555abed6e247bae171990a273147b7b3302cd Mon Sep 17 00:00:00 2001

---
 src/backend/access/heap/vacuumlazy.c          |  99 +++++++++--
 src/backend/catalog/system_views.sql          |  35 ++++
 src/backend/utils/activity/pgstat.c           |   7 +-
 src/backend/utils/activity/pgstat_relation.c  |  41 +++--
 src/backend/utils/adt/pgstatfuncs.c           | 100 +++++++----
 src/include/catalog/pg_proc.dat               |   9 +
 src/include/pgstat.h                          |  52 ++++--
 .../vacuum-extending-in-repetable-read.out    |   7 +-
 src/test/regress/expected/rules.out           |  25 +++
 .../expected/vacuum_index_statistics.out      | 165 ++++++++++++++++++
 .../expected/vacuum_tables_statistics.out     |   8 +-
 src/test/regress/parallel_schedule            |   1 +
 .../regress/sql/vacuum_index_statistics.sql   | 130 ++++++++++++++
 .../regress/sql/vacuum_tables_statistics.sql  |   3 +-
 14 files changed, 601 insertions(+), 81 deletions(-)
 create mode 100644 src/test/regress/expected/vacuum_index_statistics.out
 create mode 100644 src/test/regress/sql/vacuum_index_statistics.sql

 
+static void

 	/*
 
 	ivinfo.index = indrel;

 	ivinfo.index = indrel;

 

 
 		case VACUUM_ERRCB_PHASE_TRUNCATE:
+			if(geterrelevel() == ERROR)
+				pgstat_report_vacuum_error(errinfo->reloid, PGSTAT_EXTVAC_HEAP);
 			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 4fa9886f409..4da271dae10 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1429,3 +1429,38 @@ FROM pg_class rel
   JOIN pg_namespace ns ON ns.oid = rel.relnamespace
   LEFT JOIN pg_stat_vacuum_tables(rel.oid) stats ON true
 WHERE rel.relkind = 'r';
+
+CREATE VIEW pg_stat_vacuum_indexes AS
+SELECT
+  rel.oid as relid,
+  ns.nspname AS "schema",
+  rel.relname AS relname,
+
+  COALESCE(total_blks_read, 0) AS total_blks_read,
+  COALESCE(total_blks_hit, 0) AS total_blks_hit,
+  COALESCE(total_blks_dirtied, 0) AS total_blks_dirtied,
+  COALESCE(total_blks_written, 0) AS total_blks_written,
+
+  COALESCE(rel_blks_read, 0) AS rel_blks_read,
+  COALESCE(rel_blks_hit, 0) AS rel_blks_hit,
+
+  COALESCE(pages_deleted, 0) AS pages_deleted,
+  COALESCE(tuples_deleted, 0) AS tuples_deleted,
+
+  COALESCE(wal_records, 0) AS wal_records,
+  COALESCE(wal_fpi, 0) AS wal_fpi,
+  COALESCE(wal_bytes, 0) AS wal_bytes,
+
+  COALESCE(blk_read_time, 0) AS blk_read_time,
+  COALESCE(blk_write_time, 0) AS blk_write_time,
+
+  COALESCE(delay_time, 0) AS delay_time,
+  COALESCE(system_time, 0) AS system_time,
+  COALESCE(user_time, 0) AS user_time,
+  COALESCE(total_time, 0) AS total_time,
+  COALESCE(interrupts, 0) AS interrupts
+FROM
+  pg_class rel
+  JOIN pg_namespace ns ON ns.oid = rel.relnamespace
+  LEFT JOIN pg_stat_vacuum_indexes(rel.oid) stats ON true
+WHERE rel.relkind = 'i';
\ No newline at end of file
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c283e442f6f..843617eba25 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -1122,7 +1122,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
 	PG_TRY();
 	{
 		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
-		pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+		if (kind == PGSTAT_KIND_RELATION)
+			pgstat_build_snapshot(PGSTAT_KIND_RELATION);
 	}
 	PG_FINALLY();
 	{
@@ -1177,6 +1178,10 @@ pgstat_build_snapshot(PgStat_Kind statKind)
 		if (p->dropped)
 			continue;

 

index eba1783e51a..8f5a17e7375 100644

 static void

 

 

 

 
 
-			if (tabentry != NULL)
@@ -2215,7 +2230,18 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns)
 
 	PG_RETURN_VOID();
 }
index 776a7344285..856d04b986f 100644
@@ -12372,4 +12372,13 @@

 
 /* ----------
 
 /*

 step s1_begin_repeatable_read: 
   BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 61df9cfc64c..b2611386211 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2237,6 +2237,31 @@ pg_stat_user_tables| SELECT relid,
     autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
+    ns.nspname AS schema,
+    rel.relname,
+    COALESCE(stats.total_blks_read, (0)::bigint) AS total_blks_read,
+    COALESCE(stats.total_blks_hit, (0)::bigint) AS total_blks_hit,
+    COALESCE(stats.total_blks_dirtied, (0)::bigint) AS total_blks_dirtied,
+    COALESCE(stats.total_blks_written, (0)::bigint) AS total_blks_written,
+    COALESCE(stats.rel_blks_read, (0)::bigint) AS rel_blks_read,
+    COALESCE(stats.rel_blks_hit, (0)::bigint) AS rel_blks_hit,
+    COALESCE(stats.pages_deleted, (0)::bigint) AS pages_deleted,
+    COALESCE(stats.tuples_deleted, (0)::bigint) AS tuples_deleted,
+    COALESCE(stats.wal_records, (0)::bigint) AS wal_records,
+    COALESCE(stats.wal_fpi, (0)::bigint) AS wal_fpi,
+    COALESCE(stats.wal_bytes, (0)::numeric) AS wal_bytes,
+    COALESCE(stats.blk_read_time, (0)::double precision) AS blk_read_time,
+    COALESCE(stats.blk_write_time, (0)::double precision) AS blk_write_time,
+    COALESCE(stats.delay_time, (0)::double precision) AS delay_time,
+    COALESCE(stats.system_time, (0)::double precision) AS system_time,
+    COALESCE(stats.user_time, (0)::double precision) AS user_time,
+    COALESCE(stats.total_time, (0)::double precision) AS total_time,
+    COALESCE(stats.interrupts, 0) AS interrupts
+   FROM ((pg_class rel
+     JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace)))
+     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON (true))
+  WHERE (rel.relkind = 'i'::"char");
 pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     rel.relname,
diff --git a/src/test/regress/expected/vacuum_index_statistics.out b/src/test/regress/expected/vacuum_index_statistics.out
new file mode 100644
index 00000000000..166de176e29
--- /dev/null
+++ b/src/test/regress/expected/vacuum_index_statistics.out
@@ -0,0 +1,165 @@
+--
+-- Test cumulative vacuum stats system
+--
+-- Check the wall statistics collected during vacuum operation:
+-- number of frozen and visible pages set by vacuum;
+-- 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 
+--------------
+ on
+(1 row)
+
+-- 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 
+--------------------------
+ 
+(1 row)
+
+\set sample_size 10000
+SET vacuum_freeze_min_age = 0;
+SET vacuum_freeze_table_age = 0;
+--SET stats_fetch_consistency = snapshot;
+CREATE TABLE vestat (x int primary key) WITH (autovacuum_enabled = off, fillfactor = 10);
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+ANALYZE vestat;
+SELECT oid AS ioid from pg_class where relname = 'vestat_pkey' \gset
+DELETE FROM vestat WHERE x % 2 = 0;
+-- Before the first vacuum execution extended stats view is empty.
+SELECT vt.relname,relpages,pages_deleted,tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey |       30 |             0 |              0
+(1 row)
+
+SELECT relpages AS irp
+FROM pg_class c
+WHERE relname = 'vestat_pkey' \gset
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- The table and index extended vacuum statistics should show us that
+-- vacuum frozed pages and clean up pages, but pages_removed stayed the same
+-- because of not full table have cleaned up
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted = 0 AS pages_deleted,tuples_deleted > 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | t        | t             | t
+(1 row)
+
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+-- Look into WAL records deltas.
+SELECT wal_records > 0 AS diWR, wal_bytes > 0 AS diWB
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey';
+ diwr | diwb 
+------+------
+ t    | t
+(1 row)
+
+DELETE FROM vestat;;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- pages_removed must be increased
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd > 0 AS pages_deleted,tuples_deleted-:itd > 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | t        | t             | t
+(1 row)
+
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records-:iwr AS diwr, wal_bytes-:iwb AS diwb, wal_fpi-:ifpi AS difpi
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+-- WAL advance should be detected.
+SELECT :diwr > 0 AS diWR, :diwb > 0 AS diWB;
+ diwr | diwb 
+------+------
+ t    | t
+(1 row)
+
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
+DELETE FROM vestat WHERE x % 2 = 0;
+-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics
+-- are detected here.
+VACUUM FULL vestat;
+-- It is necessary to check the wal statistics
+CHECKPOINT;
+-- Store WAL advances into variables
+SELECT wal_records-:iwr AS diwr2, wal_bytes-:iwb AS diwb2, wal_fpi-:ifpi AS difpi2
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+-- WAL and other statistics advance should not be detected.
+SELECT :diwr2=0 AS diWR, :difpi2=0 AS iFPI, :diwb2=0 AS diWB;
+ diwr | ifpi | diwb 
+------+------+------
+ t    | t    | t
+(1 row)
+
+SELECT vt.relname,relpages-:irp < 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | t        | t             | t
+(1 row)
+
+SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset
+-- Store WAL advances into variables
+SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+DELETE FROM vestat;
+TRUNCATE vestat;
+VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat;
+-- it is necessary to check the wal statistics
+CHECKPOINT;
+-- Store WAL advances into variables after removing all tuples from the table
+SELECT wal_records-:iwr AS diwr3, wal_bytes-:iwb AS diwb3, wal_fpi-:ifpi AS difpi3
+FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset
+--There are nothing changed
+SELECT :diwr3=0 AS diWR, :difpi3=0 AS iFPI, :diwb3=0 AS diWB;
+ diwr | ifpi | diwb 
+------+------+------
+ t    | t    | t
+(1 row)
+
+--
+-- Now, the table and index is compressed into zero number of pages. Check it
+-- in vacuum extended statistics.
+-- The pages_frozen, pages_scanned values shouldn't be changed
+--
+SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted
+FROM pg_stat_vacuum_indexes vt, pg_class c
+WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey | f        | t             | t
+(1 row)
+
+SELECT min(relid) FROM pg_stat_vacuum_indexes(0);
+ min  
+------
+ 1232
+(1 row)
+
+DROP TABLE vestat;
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_statistics.out
index 064064e94b2..8bf706fdf86 100644
--- a/src/test/regress/expected/vacuum_tables_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_statistics.out
@@ -23,8 +23,6 @@ SELECT pg_stat_force_next_flush();
 (1 row)
 
 \set sample_size 10000
-SET vacuum_freeze_min_age = 0;
-SET vacuum_freeze_table_age = 0;
 --SET stats_fetch_consistency = snapshot;
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
@@ -201,9 +199,9 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 (1 row)

index 977a0472027..9847a330ed1 100644
index bc8d051aefa..ed4352566ee 100644
@@ -17,8 +17,7 @@ SET track_functions TO 'all';
 SELECT pg_stat_force_next_flush();
 
 \set sample_size 10000
-SET vacuum_freeze_min_age = 0;
-SET vacuum_freeze_table_age = 0;
+
 --SET stats_fetch_consistency = snapshot;
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
-- 
2.34.1

From e8560d296f9440558ae92e8c77518137a3dc9e58 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email-masked]>
Date: Tue, 22 Oct 2024 21:02:16 +0300
Subject: [PATCH 3/3] Machinery for grabbing an extended vacuum statistics on
 databases. It transmits vacuum statistical information about each table and
 accumulates it for the database which the table belonged.

---
 src/backend/catalog/system_views.sql          | 29 ++++++-
 src/backend/utils/activity/pgstat.c           |  2 +
 src/backend/utils/activity/pgstat_database.c  |  1 +
 src/backend/utils/activity/pgstat_relation.c  | 16 ++++
 src/backend/utils/adt/pgstatfuncs.c           | 77 +++++++++++++++++-
 src/include/catalog/pg_proc.dat               | 11 ++-
 src/include/pgstat.h                          |  3 +-
 src/test/regress/expected/rules.out           | 18 +++++
 ...ut => vacuum_tables_and_db_statistics.out} | 81 ++++++++++++++++++-
 src/test/regress/parallel_schedule            |  2 +-
 ...ql => vacuum_tables_and_db_statistics.sql} | 66 ++++++++++++++-
 11 files changed, 291 insertions(+), 15 deletions(-)
 rename src/test/regress/expected/{vacuum_tables_statistics.out => vacuum_tables_and_db_statistics.out} (77%)
 rename src/test/regress/sql/{vacuum_tables_statistics.sql => vacuum_tables_and_db_statistics.sql} (79%)

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4da271dae10..b68e0f00abd 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1424,7 +1424,6 @@ SELECT
   COALESCE(stats.user_time, 0) AS user_time,
   COALESCE(stats.total_time, 0) AS total_time,
   COALESCE(stats.interrupts, 0) AS interrupts
-
 FROM pg_class rel
   JOIN pg_namespace ns ON ns.oid = rel.relnamespace
   LEFT JOIN pg_stat_vacuum_tables(rel.oid) stats ON true
@@ -1463,4 +1462,30 @@ FROM
   pg_class rel
   JOIN pg_namespace ns ON ns.oid = rel.relnamespace
   LEFT JOIN pg_stat_vacuum_indexes(rel.oid) stats ON true
-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,
+
+  COALESCE(stats.db_blks_read, 0) AS db_blks_read,
+  COALESCE(stats.db_blks_hit, 0) AS db_blks_hit,
+  COALESCE(stats.total_blks_dirtied, 0) AS total_blks_dirtied,
+  COALESCE(stats.total_blks_written, 0) AS total_blks_written,
+
+  COALESCE(stats.wal_records, 0) AS wal_records,
+  COALESCE(stats.wal_fpi, 0) AS wal_fpi,
+  COALESCE(stats.wal_bytes, 0) AS wal_bytes,
+
+  COALESCE(stats.blk_read_time, 0) AS blk_read_time,
+  COALESCE(stats.blk_write_time, 0) AS blk_write_time,
+
+  COALESCE(stats.delay_time, 0) AS delay_time,
+  COALESCE(stats.system_time, 0) AS system_time,
+  COALESCE(stats.user_time, 0) AS user_time,
+  COALESCE(stats.total_time, 0) AS total_time,
+  COALESCE(stats.interrupts, 0) AS interrupts
+FROM
+  pg_database db
+  LEFT JOIN pg_stat_vacuum_database(db.oid) stats ON true;
\ No newline at end of file
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 843617eba25..21b29804620 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -1124,6 +1124,8 @@ pgstat_update_snapshot(PgStat_Kind kind)
 		pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
 		if (kind == PGSTAT_KIND_RELATION)
 			pgstat_build_snapshot(PGSTAT_KIND_RELATION);
+		else if (kind == PGSTAT_KIND_DATABASE)
+			pgstat_build_snapshot(PGSTAT_KIND_DATABASE);
 	}
 	PG_FINALLY();
 	{
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 29bc0909748..a060d1a4042 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -430,6 +430,7 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	pgstat_unlock_entry(entry_ref);

 	return true;
 }

 /*

 /*
index 8f5a17e7375..cac34fbe64f 100644

+static void
+tuplestore_put_for_database(Oid dbid, ReturnSetInfo *rsinfo,
+							PgStat_StatDBEntry *dbentry)
+{
+	Datum		values[EXTVACDBSTAT_COLUMNS];
+	bool		nulls[EXTVACDBSTAT_COLUMNS];
+	char		buf[256];
+	int			i = 0;
+
+	memset(nulls, 0, EXTVACDBSTAT_COLUMNS * sizeof(bool));
+
+	values[i++] = ObjectIdGetDatum(dbid);
+
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_read);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_hit);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_dirtied);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_written);
+
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.wal_records);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.wal_fpi);
+
+	/* Convert to numeric, like pg_stat_statements */
+	snprintf(buf, sizeof buf, UINT64_FORMAT, dbentry->vacuum_ext.wal_bytes);
+	values[i++] = DirectFunctionCall3(numeric_in,
+									  CStringGetDatum(buf),
+									  ObjectIdGetDatum(0),
+									  Int32GetDatum(-1));
+
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.blk_read_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.blk_write_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.delay_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.system_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.user_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.total_time);
+	values[i++] = Int32GetDatum(dbentry->vacuum_ext.interrupts);
+
+	Assert(i == rsinfo->setDesc->natts);
+	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+}
+
 static void
 tuplestore_put_for_relation(Oid relid, ReturnSetInfo *rsinfo,
 							PgStat_StatTabEntry *tabentry)
@@ -2195,8 +2236,9 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 		if (OidIsValid(relid))
 		{
 			tabentry = pgstat_fetch_stat_tabentry(relid);
-			if (tabentry == NULL || tabentry->vacuum_ext.type != type)
-				/* Table don't exists or isn't an heap relation. */
+
+			if ((tabentry == NULL || tabentry->vacuum_ext.type != type))
+				/* Table don't exists or isn't a heap or index relation. */
 				return;
 
@@ -2204,7 +2246,7 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 
 			pgstat_update_snapshot(PGSTAT_KIND_RELATION);
 
@@ -2222,6 +2264,22 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 			}
 		}
 	}
+	else if (type == PGSTAT_EXTVAC_DB)
+	{
+		PgStat_StatDBEntry	    *dbentry;
+		Oid						dbid = PG_GETARG_OID(0);
+
+		if (OidIsValid(dbid))
+		{
+			dbentry = pgstat_fetch_stat_dbentry(dbid);
+
+			if (dbentry == NULL)
+				/* Database doesn't exist */
+				return;
+
+			tuplestore_put_for_database(dbid, rsinfo, dbentry);
+		}
+	}
 }
 
 /*
@@ -2244,4 +2302,15 @@ pg_stat_vacuum_indexes(PG_FUNCTION_ARGS)
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_INDEX, EXTVACIDXSTAT_COLUMNS);
 
 	PG_RETURN_VOID();
-}
index 856d04b986f..f5f97a80cf5 100644
@@ -12380,5 +12380,14 @@
 
 /* ----------
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b2611386211..f8112d54f52 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2237,6 +2237,24 @@ pg_stat_user_tables| SELECT relid,
     autoanalyze_count
    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,
+    COALESCE(stats.db_blks_read, (0)::bigint) AS db_blks_read,
+    COALESCE(stats.db_blks_hit, (0)::bigint) AS db_blks_hit,
+    COALESCE(stats.total_blks_dirtied, (0)::bigint) AS total_blks_dirtied,
+    COALESCE(stats.total_blks_written, (0)::bigint) AS total_blks_written,
+    COALESCE(stats.wal_records, (0)::bigint) AS wal_records,
+    COALESCE(stats.wal_fpi, (0)::bigint) AS wal_fpi,
+    COALESCE(stats.wal_bytes, (0)::numeric) AS wal_bytes,
+    COALESCE(stats.blk_read_time, (0)::double precision) AS blk_read_time,
+    COALESCE(stats.blk_write_time, (0)::double precision) AS blk_write_time,
+    COALESCE(stats.delay_time, (0)::double precision) AS delay_time,
+    COALESCE(stats.system_time, (0)::double precision) AS system_time,
+    COALESCE(stats.user_time, (0)::double precision) AS user_time,
+    COALESCE(stats.total_time, (0)::double precision) AS total_time,
+    COALESCE(stats.interrupts, 0) AS interrupts
+   FROM (pg_database db
+     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON (true));
 pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     rel.relname,
diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
similarity index 77%
rename from src/test/regress/expected/vacuum_tables_statistics.out
rename to src/test/regress/expected/vacuum_tables_and_db_statistics.out
index 8bf706fdf86..7a0b3ba96e1 100644
--- a/src/test/regress/expected/vacuum_tables_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -6,6 +6,9 @@
 -- number of frozen and visible pages removed by backend.
 -- Statistic wal_fpi is not displayed in this test because its behavior is unstable.
 --
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
  track_counts 
@@ -175,10 +178,7 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 
 SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
 FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
-UPDATE vestat SET x = x1001;
-ERROR:  column "x1001" does not exist
-LINE 1: UPDATE vestat SET x = x1001;
-                              ^
+UPDATE vestat SET x = x+1001;
 VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 SELECT pages_frozen > :pf AS pages_frozen,pages_all_visible > :pv AS pages_all_visible,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 WHERE relname = 'vestat';
@@ -204,4 +204,77 @@ SELECT min(relid) FROM pg_stat_vacuum_tables(0) where relid > 0;
  1213
 (1 row)
 
index 9847a330ed1..1ba32b87cf5 100644
index ed4352566ee..a3ddc9419de 100644
 
+CREATE DATABASE regression_statistic_vacuum_db;
+CREATE DATABASE regression_statistic_vacuum_db1;
+\c regression_statistic_vacuum_db;
+
 -- conditio sine qua non
 SHOW track_counts;  -- must be on
 -- not enabled by default, but we want to test it...
@@ -140,7 +144,7 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
 FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
 
-UPDATE vestat SET x = x1001;
+UPDATE vestat SET x = x+1001;
 VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 
 SELECT pages_frozen > :pf AS pages_frozen,pages_all_visible > :pv AS pages_all_visible,rev_all_frozen_pages > :hafp AS rev_all_frozen_pages,rev_all_visible_pages > :havp AS rev_all_visible_pages
@@ -156,4 +160,62 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';

---

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b63c1804b41..b68e0f00abd 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1392,48 +1392,42 @@ SELECT
   ns.nspname AS "schema",
   rel.relname AS 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_scanned,
-  stats.pages_removed,
-  stats.pages_frozen,
-  stats.pages_all_visible,
-  stats.tuples_deleted,
-  stats.tuples_frozen,
-  stats.dead_tuples,
-
-  stats.index_vacuum_count,
-  stats.rev_all_frozen_pages,
-  stats.rev_all_visible_pages,
-
-  stats.wal_records,
-  stats.wal_fpi,
-  stats.wal_bytes,
-
-  stats.blk_read_time,
-  stats.blk_write_time,
-
-  stats.delay_time,
-  stats.system_time,
-  stats.user_time,
-  stats.total_time,
-  stats.interrupts
-FROM
-  pg_database db,
-  pg_class rel,
-  pg_namespace ns,
-  pg_stat_vacuum_tables(rel.oid) stats
-WHERE
-  db.datname = current_database() AND
-  rel.oid = stats.relid AND
-  ns.oid = rel.relnamespace AND
-  rel.relkind = 'r';
+  COALESCE(stats.total_blks_read, 0) AS total_blks_read,
+  COALESCE(stats.total_blks_hit, 0) AS total_blks_hit,
+  COALESCE(stats.total_blks_dirtied, 0) AS total_blks_dirtied,
+  COALESCE(stats.total_blks_written, 0) AS total_blks_written,
+
+  COALESCE(stats.rel_blks_read, 0) AS rel_blks_read,
+  COALESCE(stats.rel_blks_hit, 0) AS rel_blks_hit,
+
+  COALESCE(stats.pages_scanned, 0) AS pages_scanned,
+  COALESCE(stats.pages_removed, 0) AS pages_removed,
+  COALESCE(stats.pages_frozen, 0) AS pages_frozen,
+  COALESCE(stats.pages_all_visible, 0) AS pages_all_visible,
+  COALESCE(stats.tuples_deleted, 0) AS tuples_deleted,
+  COALESCE(stats.tuples_frozen, 0) AS tuples_frozen,
+  COALESCE(stats.dead_tuples, 0) AS dead_tuples,
+
+  COALESCE(stats.index_vacuum_count, 0) AS index_vacuum_count,
+  COALESCE(stats.rev_all_frozen_pages, 0) AS rev_all_frozen_pages,
+  COALESCE(stats.rev_all_visible_pages, 0) AS rev_all_visible_pages,
+
+  COALESCE(stats.wal_records, 0) AS wal_records,
+  COALESCE(stats.wal_fpi, 0) AS wal_fpi,
+  COALESCE(stats.wal_bytes, 0) AS wal_bytes,
+
+  COALESCE(stats.blk_read_time, 0) AS blk_read_time,
+  COALESCE(stats.blk_write_time, 0) AS blk_write_time,
+
+  COALESCE(stats.delay_time, 0) AS delay_time,
+  COALESCE(stats.system_time, 0) AS system_time,
+  COALESCE(stats.user_time, 0) AS user_time,
+  COALESCE(stats.total_time, 0) AS total_time,
+  COALESCE(stats.interrupts, 0) AS interrupts
+FROM pg_class rel
+  JOIN pg_namespace ns ON ns.oid = rel.relnamespace
+  LEFT JOIN pg_stat_vacuum_tables(rel.oid) stats ON true
+WHERE rel.relkind = 'r';
 
 CREATE VIEW pg_stat_vacuum_indexes AS
 SELECT
@@ -1441,64 +1435,57 @@ SELECT
   ns.nspname AS "schema",
   rel.relname AS relname,
 
-  stats.total_blks_read,
-  stats.total_blks_hit,
-  stats.total_blks_dirtied,
-  stats.total_blks_written,
+  COALESCE(total_blks_read, 0) AS total_blks_read,
+  COALESCE(total_blks_hit, 0) AS total_blks_hit,
+  COALESCE(total_blks_dirtied, 0) AS total_blks_dirtied,
+  COALESCE(total_blks_written, 0) AS total_blks_written,
 
-  stats.rel_blks_read,
-  stats.rel_blks_hit,
+  COALESCE(rel_blks_read, 0) AS rel_blks_read,
+  COALESCE(rel_blks_hit, 0) AS rel_blks_hit,
 
-  stats.pages_deleted,
-  stats.tuples_deleted,
+  COALESCE(pages_deleted, 0) AS pages_deleted,
+  COALESCE(tuples_deleted, 0) AS tuples_deleted,
 
-  stats.wal_records,
-  stats.wal_fpi,
-  stats.wal_bytes,
+  COALESCE(wal_records, 0) AS wal_records,
+  COALESCE(wal_fpi, 0) AS wal_fpi,
+  COALESCE(wal_bytes, 0) AS wal_bytes,
 
-  stats.blk_read_time,
-  stats.blk_write_time,
+  COALESCE(blk_read_time, 0) AS blk_read_time,
+  COALESCE(blk_write_time, 0) AS blk_write_time,
 
-  stats.delay_time,
-  stats.system_time,
-  stats.user_time,
-  stats.total_time,
-  stats.interrupts
+  COALESCE(delay_time, 0) AS delay_time,
+  COALESCE(system_time, 0) AS system_time,
+  COALESCE(user_time, 0) AS user_time,
+  COALESCE(total_time, 0) AS total_time,
+  COALESCE(interrupts, 0) AS interrupts
 FROM
-  pg_database db,
-  pg_class rel,
-  pg_namespace ns,
-  pg_stat_vacuum_indexes(rel.oid) stats
-WHERE
-  db.datname = current_database() AND
-  rel.oid = stats.relid AND
-  ns.oid = rel.relnamespace AND
-  rel.relkind = 'i';
+  pg_class rel
+  JOIN pg_namespace ns ON ns.oid = rel.relnamespace
+  LEFT JOIN pg_stat_vacuum_indexes(rel.oid) stats ON true
+WHERE rel.relkind = 'i';
 
 CREATE VIEW pg_stat_vacuum_database AS
 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,
+  COALESCE(stats.db_blks_read, 0) AS db_blks_read,
+  COALESCE(stats.db_blks_hit, 0) AS db_blks_hit,
+  COALESCE(stats.total_blks_dirtied, 0) AS total_blks_dirtied,
+  COALESCE(stats.total_blks_written, 0) AS total_blks_written,
 
-  stats.blk_read_time,
-  stats.blk_write_time,
+  COALESCE(stats.wal_records, 0) AS wal_records,
+  COALESCE(stats.wal_fpi, 0) AS wal_fpi,
+  COALESCE(stats.wal_bytes, 0) AS wal_bytes,
 
-  stats.delay_time,
-  stats.system_time,
-  stats.user_time,
-  stats.total_time,
+  COALESCE(stats.blk_read_time, 0) AS blk_read_time,
+  COALESCE(stats.blk_write_time, 0) AS blk_write_time,
 
-  stats.interrupts
+  COALESCE(stats.delay_time, 0) AS delay_time,
+  COALESCE(stats.system_time, 0) AS system_time,
+  COALESCE(stats.user_time, 0) AS user_time,
+  COALESCE(stats.total_time, 0) AS total_time,
+  COALESCE(stats.interrupts, 0) AS interrupts
 FROM
-  pg_database db LEFT JOIN pg_stat_vacuum_database(db.oid) stats
-ON
-  db.oid = stats.dboid;
\ No newline at end of file
+  pg_database db
+  LEFT JOIN pg_stat_vacuum_database(db.oid) stats ON true;
\ No newline at end of file
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 4d1c099b37e..cac34fbe64f 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2107,7 +2107,7 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
 static void
 tuplestore_put_for_database(Oid dbid, ReturnSetInfo *rsinfo,
-							PgStatShared_Database *dbentry)
+							PgStat_StatDBEntry *dbentry)
 {
 	Datum		values[EXTVACDBSTAT_COLUMNS];
 	bool		nulls[EXTVACDBSTAT_COLUMNS];
@@ -2118,28 +2118,28 @@ tuplestore_put_for_database(Oid dbid, ReturnSetInfo *rsinfo,
 
 	values[i++] = ObjectIdGetDatum(dbid);
 
-	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_read);
-	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_hit);
-	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_dirtied);
-	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.total_blks_written);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_read);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_hit);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_dirtied);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.total_blks_written);
 
-	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.wal_records);
-	values[i++] = Int64GetDatum(dbentry->stats.vacuum_ext.wal_fpi);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.wal_records);
+	values[i++] = Int64GetDatum(dbentry->vacuum_ext.wal_fpi);
 
 	/* Convert to numeric, like pg_stat_statements */
-	snprintf(buf, sizeof buf, UINT64_FORMAT, dbentry->stats.vacuum_ext.wal_bytes);
+	snprintf(buf, sizeof buf, UINT64_FORMAT, dbentry->vacuum_ext.wal_bytes);
 	values[i++] = DirectFunctionCall3(numeric_in,
 									  CStringGetDatum(buf),
 									  ObjectIdGetDatum(0),
 									  Int32GetDatum(-1));
 
-	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.blk_read_time);
-	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.blk_write_time);
-	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.delay_time);
-	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.system_time);
-	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.user_time);
-	values[i++] = Float8GetDatum(dbentry->stats.vacuum_ext.total_time);
-	values[i++] = Int32GetDatum(dbentry->stats.vacuum_ext.interrupts);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.blk_read_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.blk_write_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.delay_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.system_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.user_time);
+	values[i++] = Float8GetDatum(dbentry->vacuum_ext.total_time);
+	values[i++] = Int32GetDatum(dbentry->vacuum_ext.interrupts);
 
 	Assert(i == rsinfo->setDesc->natts);
 	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -2236,8 +2236,9 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 		if (OidIsValid(relid))
 		{
 			tabentry = pgstat_fetch_stat_tabentry(relid);
-			if (tabentry == NULL || tabentry->vacuum_ext.type != type)
-				/* Table don't exists or isn't an heap relation. */
+
+			if ((tabentry == NULL || tabentry->vacuum_ext.type != type))
+				/* Table don't exists or isn't a heap or index relation. */
 				return;
 
@@ -2245,7 +2246,7 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 
 			pgstat_update_snapshot(PGSTAT_KIND_RELATION);
 
@@ -2265,22 +2266,18 @@ pg_stats_vacuum(FunctionCallInfo fcinfo, ExtVacReportType type, int ncolumns)
 	}
 	else if (type == PGSTAT_EXTVAC_DB)
 	{
-		PgStatShared_Database	   *dbentry;
-		PgStat_EntryRef 		   *entry_ref;
-		Oid							dbid = PG_GETARG_OID(0);
+		PgStat_StatDBEntry	    *dbentry;
+		Oid						dbid = PG_GETARG_OID(0);
 
 		if (OidIsValid(dbid))
 		{
-			entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
-											dbid, InvalidOid, false);
-			dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
+			dbentry = pgstat_fetch_stat_dbentry(dbid);
 
 			if (dbentry == NULL)
-				/* Table doesn't exist or isn't a heap relation */
+				/* Database doesn't exist */
 				return;
 
 			tuplestore_put_for_database(dbid, rsinfo, dbentry);
-			pgstat_unlock_entry(entry_ref);
 		}
 	}
 }
@@ -2316,4 +2313,4 @@ pg_stat_vacuum_database(PG_FUNCTION_ARGS)
 	pg_stats_vacuum(fcinfo, PGSTAT_EXTVAC_DB, EXTVACDBSTAT_COLUMNS);
 
 	PG_RETURN_VOID();
-}
\ No newline at end of file
+ }
\ No newline at end of file
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 8359cf3e984..f8112d54f52 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2239,82 +2239,80 @@ pg_stat_user_tables| SELECT relid,
   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.system_time,
-    stats.user_time,
-    stats.total_time,
-    stats.interrupts
+    COALESCE(stats.db_blks_read, (0)::bigint) AS db_blks_read,
+    COALESCE(stats.db_blks_hit, (0)::bigint) AS db_blks_hit,
+    COALESCE(stats.total_blks_dirtied, (0)::bigint) AS total_blks_dirtied,
+    COALESCE(stats.total_blks_written, (0)::bigint) AS total_blks_written,
+    COALESCE(stats.wal_records, (0)::bigint) AS wal_records,
+    COALESCE(stats.wal_fpi, (0)::bigint) AS wal_fpi,
+    COALESCE(stats.wal_bytes, (0)::numeric) AS wal_bytes,
+    COALESCE(stats.blk_read_time, (0)::double precision) AS blk_read_time,
+    COALESCE(stats.blk_write_time, (0)::double precision) AS blk_write_time,
+    COALESCE(stats.delay_time, (0)::double precision) AS delay_time,
+    COALESCE(stats.system_time, (0)::double precision) AS system_time,
+    COALESCE(stats.user_time, (0)::double precision) AS user_time,
+    COALESCE(stats.total_time, (0)::double precision) AS total_time,
+    COALESCE(stats.interrupts, 0) AS interrupts
    FROM (pg_database db
-     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON ((db.oid = stats.dboid)));
+     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON (true));
 pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     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.system_time,
-    stats.user_time,
-    stats.total_time,
-    stats.interrupts
-   FROM pg_database db,
-    pg_class rel,
-    pg_namespace ns,
-    LATERAL pg_stat_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, system_time, user_time, total_time, interrupts)
-  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace) AND (rel.relkind = 'i'::"char"));
+    COALESCE(stats.total_blks_read, (0)::bigint) AS total_blks_read,
+    COALESCE(stats.total_blks_hit, (0)::bigint) AS total_blks_hit,
+    COALESCE(stats.total_blks_dirtied, (0)::bigint) AS total_blks_dirtied,
+    COALESCE(stats.total_blks_written, (0)::bigint) AS total_blks_written,
+    COALESCE(stats.rel_blks_read, (0)::bigint) AS rel_blks_read,
+    COALESCE(stats.rel_blks_hit, (0)::bigint) AS rel_blks_hit,
+    COALESCE(stats.pages_deleted, (0)::bigint) AS pages_deleted,
+    COALESCE(stats.tuples_deleted, (0)::bigint) AS tuples_deleted,
+    COALESCE(stats.wal_records, (0)::bigint) AS wal_records,
+    COALESCE(stats.wal_fpi, (0)::bigint) AS wal_fpi,
+    COALESCE(stats.wal_bytes, (0)::numeric) AS wal_bytes,
+    COALESCE(stats.blk_read_time, (0)::double precision) AS blk_read_time,
+    COALESCE(stats.blk_write_time, (0)::double precision) AS blk_write_time,
+    COALESCE(stats.delay_time, (0)::double precision) AS delay_time,
+    COALESCE(stats.system_time, (0)::double precision) AS system_time,
+    COALESCE(stats.user_time, (0)::double precision) AS user_time,
+    COALESCE(stats.total_time, (0)::double precision) AS total_time,
+    COALESCE(stats.interrupts, 0) AS interrupts
+   FROM ((pg_class rel
+     JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace)))
+     LEFT JOIN LATERAL pg_stat_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, system_time, user_time, total_time, interrupts) ON (true))
+  WHERE (rel.relkind = 'i'::"char");
 pg_stat_vacuum_tables| SELECT rel.oid AS relid,
     ns.nspname AS schema,
     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_scanned,
-    stats.pages_removed,
-    stats.pages_frozen,
-    stats.pages_all_visible,
-    stats.tuples_deleted,
-    stats.tuples_frozen,
-    stats.dead_tuples,
-    stats.index_vacuum_count,
-    stats.rev_all_frozen_pages,
-    stats.rev_all_visible_pages,
-    stats.wal_records,
-    stats.wal_fpi,
-    stats.wal_bytes,
-    stats.blk_read_time,
-    stats.blk_write_time,
-    stats.delay_time,
-    stats.system_time,
-    stats.user_time,
-    stats.total_time,
-    stats.interrupts
-   FROM pg_database db,
-    pg_class rel,
-    pg_namespace ns,
-    LATERAL pg_stat_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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts)
-  WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace) AND (rel.relkind = 'r'::"char"));
+    COALESCE(stats.total_blks_read, (0)::bigint) AS total_blks_read,
+    COALESCE(stats.total_blks_hit, (0)::bigint) AS total_blks_hit,
+    COALESCE(stats.total_blks_dirtied, (0)::bigint) AS total_blks_dirtied,
+    COALESCE(stats.total_blks_written, (0)::bigint) AS total_blks_written,
+    COALESCE(stats.rel_blks_read, (0)::bigint) AS rel_blks_read,
+    COALESCE(stats.rel_blks_hit, (0)::bigint) AS rel_blks_hit,
+    COALESCE(stats.pages_scanned, (0)::bigint) AS pages_scanned,
+    COALESCE(stats.pages_removed, (0)::bigint) AS pages_removed,
+    COALESCE(stats.pages_frozen, (0)::bigint) AS pages_frozen,
+    COALESCE(stats.pages_all_visible, (0)::bigint) AS pages_all_visible,
+    COALESCE(stats.tuples_deleted, (0)::bigint) AS tuples_deleted,
+    COALESCE(stats.tuples_frozen, (0)::bigint) AS tuples_frozen,
+    COALESCE(stats.dead_tuples, (0)::bigint) AS dead_tuples,
+    COALESCE(stats.index_vacuum_count, (0)::bigint) AS index_vacuum_count,
+    COALESCE(stats.rev_all_frozen_pages, (0)::bigint) AS rev_all_frozen_pages,
+    COALESCE(stats.rev_all_visible_pages, (0)::bigint) AS rev_all_visible_pages,
+    COALESCE(stats.wal_records, (0)::bigint) AS wal_records,
+    COALESCE(stats.wal_fpi, (0)::bigint) AS wal_fpi,
+    COALESCE(stats.wal_bytes, (0)::numeric) AS wal_bytes,
+    COALESCE(stats.blk_read_time, (0)::double precision) AS blk_read_time,
+    COALESCE(stats.blk_write_time, (0)::double precision) AS blk_write_time,
+    COALESCE(stats.delay_time, (0)::double precision) AS delay_time,
+    COALESCE(stats.system_time, (0)::double precision) AS system_time,
+    COALESCE(stats.user_time, (0)::double precision) AS user_time,
+    COALESCE(stats.total_time, (0)::double precision) AS total_time,
+    COALESCE(stats.interrupts, 0) AS interrupts
+   FROM ((pg_class rel
+     JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace)))
+     LEFT JOIN LATERAL pg_stat_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, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts) ON (true))
+  WHERE (rel.relkind = 'r'::"char");
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
     wal_bytes,
diff --git a/src/test/regress/expected/vacuum_index_statistics.out b/src/test/regress/expected/vacuum_index_statistics.out
index 4f6e305710e..166de176e29 100644
--- a/src/test/regress/expected/vacuum_index_statistics.out
+++ b/src/test/regress/expected/vacuum_index_statistics.out
@@ -35,9 +35,10 @@ DELETE FROM vestat WHERE x % 2 = 0;
 SELECT vt.relname,relpages,pages_deleted,tuples_deleted
 FROM pg_stat_vacuum_indexes vt, pg_class c
 WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid;
- relname | relpages | pages_deleted | tuples_deleted 
----------+----------+---------------+----------------
-(0 rows)
+   relname   | relpages | pages_deleted | tuples_deleted 
+-------------+----------+---------------+----------------
+ vestat_pkey |       30 |             0 |              0
+(1 row)
 
 SELECT relpages AS irp
 FROM pg_class c
diff --git a/src/test/regress/expected/vacuum_tables_and_db_statistics.out b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
index 94dd3214349..7a0b3ba96e1 100644
--- a/src/test/regress/expected/vacuum_tables_and_db_statistics.out
+++ b/src/test/regress/expected/vacuum_tables_and_db_statistics.out
@@ -26,8 +26,6 @@ SELECT pg_stat_force_next_flush();
 (1 row)
 
 \set sample_size 10000
-SET vacuum_freeze_min_age = 0;
-SET vacuum_freeze_table_age = 0;
 --SET stats_fetch_consistency = snapshot;
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
@@ -40,7 +38,8 @@ FROM pg_stat_vacuum_tables vt, pg_class c
 WHERE vt.relname = 'vestat' AND vt.relid = c.oid;
  relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed 
 ---------+--------------+----------------+----------+---------------+---------------
-(0 rows)
+ vestat  |            0 |              0 |      455 |             0 |             0
+(1 row)
 
 SELECT relpages AS rp
 FROM pg_class c
@@ -179,10 +178,7 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 
 SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
 FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
-UPDATE vestat SET x = x1001;
-ERROR:  column "x1001" does not exist
-LINE 1: UPDATE vestat SET x = x1001;
-                              ^
+UPDATE vestat SET x = x+1001;
 VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 SELECT pages_frozen > :pf AS pages_frozen,pages_all_visible > :pv AS pages_all_visible,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 WHERE relname = 'vestat';
@@ -259,8 +255,6 @@ WHERE dbname = 'regression_statistic_vacuum_db';
 (1 row)
 
 \c regression_statistic_vacuum_db
-RESET vacuum_freeze_min_age;
-RESET vacuum_freeze_table_age;
 DROP TABLE vestat CASCADE;
 \c regression_statistic_vacuum_db1;
 SELECT count(*)
diff --git a/src/test/regress/sql/vacuum_tables_and_db_statistics.sql b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
index af1281b3b63..a3ddc9419de 100644
--- a/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
+++ b/src/test/regress/sql/vacuum_tables_and_db_statistics.sql
@@ -21,8 +21,7 @@ SET track_functions TO 'all';
 SELECT pg_stat_force_next_flush();
 
 \set sample_size 10000
-SET vacuum_freeze_min_age = 0;
-SET vacuum_freeze_table_age = 0;
+
 --SET stats_fetch_consistency = snapshot;
 CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10);
 INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x;
@@ -145,7 +144,7 @@ FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
 SELECT pages_frozen AS pf, pages_all_visible AS pv, rev_all_frozen_pages AS hafp,rev_all_visible_pages AS havp
 FROM pg_stat_vacuum_tables WHERE relname = 'vestat' \gset
 
-UPDATE vestat SET x = x1001;
+UPDATE vestat SET x = x+1001;
 VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat;
 
 SELECT pages_frozen > :pf AS pages_frozen,pages_all_visible > :pv AS pages_all_visible,rev_all_frozen_pages > :hafp AS rev_all_frozen_pages,rev_all_visible_pages > :havp AS rev_all_visible_pages
@@ -204,8 +203,6 @@ WHERE dbname = 'regression_statistic_vacuum_db';
 
 \c regression_statistic_vacuum_db
 
-RESET vacuum_freeze_min_age;
-RESET vacuum_freeze_table_age;
 DROP TABLE vestat CASCADE;
 
 \c regression_statistic_vacuum_db1;

---

From: Alexander Korotkov <[email-masked]>
Date: Mon, 28 Oct 2024 15:40:04 +0200

> If I missed something or misunderstood, can you explain in more detail?

Actually, I mean why do we need a possibility to return statistics for
all tables/indexes in one function call?  User anyway is supposed to
use pg_stat_vacuum_indexes/pg_stat_vacuum_tables view, which do
function calls one per relation.  I suppose we can get rid of
possibility to get all the objects in one function call and just
return a tuple from the functions like other pgstatfuncs.c functions
do.

------
Regards,
Alexander Korotkov
Supabase

---

From: Alena Rybakina <[email-masked]>
Date: Tue, 29 Oct 2024 14:02:13 +0300

> return a tuple from the functions like other pgstatfuncs.c functions
I haven’t thought about this before and agree with you. Thanks for the 
clarification! I'll fix the patch this evening and release the updated 
version.

-- 
Regards,
Alena Rybakina
Postgres Professional

---

From: Alena Rybakina <[email-masked]>
Date: Sat, 2 Nov 2024 15:24:31 +0300

Hi!

> clarification! I'll fix the patch this evening and release the updated 

I updated the patches as per your suggestion. You can see it here [0].

[0] 
https://www.postgresql.org/message-id/85b963fe-5977-43aa-9241-75b862abcc69%40postgrespro.ru

---

From: Ilia Evdokimov <[email-masked]>
Date: Thu, 7 Nov 2024 17:49:23 +0300

On 22.10.2024 22:30, Alena Rybakina wrote:
> through the existing pgstat_fetch_stat_dbentry function and fixed 
> couple of comments.
>

Thank you for fixing it.

1) I have found some typos in the test output files (out-files) when 
running 'make check' and 'make check-world'. These typos might cause 
minor discrepancies in test results. You may already be aware of them, 
but I wanted to bring them to your attention in case they haven't been 
noticed. I believe these can be fixed quickly.

2) Additionally, I observed that when we create a table and insert some 
rows, executing the VACUUM FULL command does not update the information 
in the 'pg_stat_get_vacuum_tables' However, running the VACUUM command 
does update this information as expected. This seems inconsistent, and 
it might be a bug.

Example:
CREATE TABLE t (i INT, j INT) WITH (autovacuum_enabled = false);
INSERT INTO t SELECT i/10, i/100 FROM  GENERATE_SERIES(1,1000000) i;
SELECT * FROM pg_stat_get_vacuum_tables WHERE relname = 't';
schema | relname |    relid | total_blks_read | .........
-----------+------------+---------+----------------------+---------
    public | t            | 21416 |                       0 | ......
(1 row)

VACUUM FULL;
SELECT * FROM pg_stat_get_vacuum_tables WHERE relname = 't';
schema | relname |    relid | total_blks_read | .........
-----------+------------+---------+----------------------+---------
    public | t            | 21416 |                       0 | ......
(1 row)

VACUUM;
SELECT * FROM pg_stat_get_vacuum_tables WHERE relname = 't';
schema | relname |    relid | total_blks_read | .........
-----------+------------+---------+----------------------+---------
    public | t            | 21416 |                 4425 | ......
(1 row)

Regards,
Ilia Evdokimov,
Tantor Labs LLC.

---

From: Andrei Lepikhov <[email-masked]>
Date: Tue, 22 Apr 2025 20:23:57 +0200

> return a tuple from the functions like other pgstatfuncs.c functions
I suppose it was designed this way because databases may contain 
thousands of tables and indexes - remember, at least, partitions. But it 
may be okay to use the SRF_FIRSTCALL_INIT / SRF_RETURN_NEXT API. I think 
by registering a prosupport routine predicting cost and rows of these 
calls, we may let the planner build adequate plans for queries involving 
those stats - people will definitely join it with something else in the 
database.

-- 
regards, Andrei Lepikhov

---

From: Alena Rybakina <[email-masked]>
Date: Fri, 9 May 2025 15:31:11 +0300

Hi!

On 22.04.2025 21:23, Andrei Lepikhov wrote:
> thousands of tables and indexes - remember, at least, partitions. But 
> it may be okay to use the SRF_FIRSTCALL_INIT / SRF_RETURN_NEXT API. I 
> think by registering a prosupport routine predicting cost and rows of 
> these calls, we may let the planner build adequate plans for queries 
> involving those stats - people will definitely join it with something 
> else in the database.
>
I think we can add this, but first we need to answer the main question - 
are there cases when we have statistics for a relation that are not in 
pg_class? After all, we have views that show vacuum statistics for all 
relations for objects stored in pg_class.

+CREATE VIEW pg_stat_vacuum_tables AS
...
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';

I tend to think that such a case will happen because to solve the 
problem with the memory consumed for storing vacuum statistics, we need 
to store them separately from the relations' statistics (I already wrote 
the code here [0]), so
the approach with the output of all statistics from a snapshot, as we 
did here [1] and removed this approach here [2] and this approach now 
makes sense and it is worth organizing it as you suggest.

I can add the code if no one is against it.

[0] 
https://www.postgresql.org/message-id/2a04ad18-5572-4633-848b-eb57209e7ac0%40postgrespro.ru

[1] 
https://www.postgresql.org/message-id/995657bc-9966-47c0-b085-4c5e8886d249%40postgrespro.ru

[2] 
https://www.postgresql.org/message-id/CAPpHfdvSo3mfH%3D2m4ADCHAuN%3D22SnBY3TrPaPbGKTw3r_Jaw7Q%40mail.gmail.com

-- 
Regards,
Alena Rybakina
Postgres Professional