public inbox for [email protected]
help / color / mirror / Atom feedFrom: Alena Rybakina <[email protected]>
To: pgsql-hackers <[email protected]>
Cc: Alexander Korotkov <[email protected]>
Cc: Amit Kapila <[email protected]>
Cc: Jim Nasby <[email protected]>
Cc: Bertrand Drouvot <[email protected]>
Cc: Kirill Reshke <[email protected]>
Cc: Andrei Zubkov <[email protected]>
Cc: Masahiko Sawada <[email protected]>
Cc: Melanie Plageman <[email protected]>
Cc: jian he <[email protected]>
Cc: [email protected]
Cc: Sami Imseih <[email protected]>
Cc: vignesh C <[email protected]>
Cc: Ilia Evdokimov <[email protected]>
Cc: Alena Rybakina <[email protected]>
Subject: Re: Vacuum statistics
Date: Sun, 21 Dec 2025 02:36:42 +0300
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<CAMFBP2oXkhX_k9FTqtW-LdTBepVq0PDuBEGO8-LpNGbyHTBrNw@mail.gmail.com>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<CAA4eK1JOUn+EqWSfRgKfgBZOXT7Q2dw2enmSZZgOhoMOFwopPA@mail.gmail.com>
<[email protected]>
<CAPpHfdtQd29O15Cmp1qeqTCerQF0Y+BGh63qtX3RkA7k=0TZ1Q@mail.gmail.com>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
Hi,
I’ve added some changes to one of the approaches and also did additional
cleanup and stabilization work on the vacuum statistics tests. Specifically:
* I moved the vacuum statistics tests into the tests tab and made them
more stable. For slower machines, vacuum is now triggered inside the
statistics wait function. Previously, some backends didn’t have
enough time to release the lock, which could lead to differences
because the vacuum hadn’t fully completed yet.
* I also ran the backend tests and fixed a couple of minor issues
along the way.
* I ran pgindent to clean up and normalize the formatting.
For now, I’ve temporarily removed collecting statistics related to
database-level errors when vacuum is forced to stop. I’m currently stuck
on how to properly expose statistics for cluster-level objects, since
their dbid is 0.
At the moment, only the second test still looks odd, and I haven’t fully
figured out why yet. It seems like aggressive vacuum can no longer be
triggered the same way as before with the current gucs, but I’m still
investigating this.
Best regards,
Alena Rybakina
From f96d5079774fe129fff32761bba4ab9089e491bd Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Tue, 9 Dec 2025 09:56:34 +0300
Subject: [PATCH 1/5] Machinery for grabbing an extended vacuum statistics on
table 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).
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 is a 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 is a number of pages that are marked as all-visible in vm during
vacuum.
wraparound_failsafe_count is a number of times when the vacuum starts urgent cleanup
to prevent wraparound problem which is critical for the database.
Authors: Alena Rybakina <[email protected]>,
Andrei Lepikhov <[email protected]>,
Andrei Zubkov <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>, Masahiko Sawada <[email protected]>,
Ilia Evdokimov <[email protected]>, jian he <[email protected]>,
Kirill Reshke <[email protected]>, Alexander Korotkov <[email protected]>,
Jim Nasby <[email protected]>, Sami Imseih <[email protected]>,
Karina Litskevich <[email protected]>
---
src/backend/access/heap/vacuumlazy.c | 145 ++++-
src/backend/access/heap/visibilitymap.c | 10 +
src/backend/catalog/system_views.sql | 52 +-
src/backend/commands/vacuum.c | 4 +
src/backend/commands/vacuumparallel.c | 1 +
src/backend/utils/activity/pgstat_relation.c | 46 +-
src/backend/utils/adt/pgstatfuncs.c | 86 +++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_proc.dat | 18 +
src/include/commands/vacuum.h | 1 +
src/include/pgstat.h | 92 ++-
.../vacuum-extending-in-repetable-read.out | 53 ++
src/test/isolation/isolation_schedule | 1 +
.../vacuum-extending-in-repetable-read.spec | 53 ++
.../t/050_vacuum_extending_basic_test.pl | 571 ++++++++++++++++++
.../t/051_vacuum_extending_freeze_test.pl | 395 ++++++++++++
src/test/regress/expected/rules.out | 44 +-
src/test/regress/parallel_schedule | 2 +-
18 files changed, 1565 insertions(+), 10 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/recovery/t/050_vacuum_extending_basic_test.pl
create mode 100644 src/test/recovery/t/051_vacuum_extending_freeze_test.pl
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 30778a15639..66e09d0a0cf 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -289,6 +289,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 */
@@ -407,6 +408,10 @@ typedef struct LVRelState
* been permanently disabled.
*/
BlockNumber eager_scan_remaining_fails;
+
+ int32 wraparound_failsafe_count; /* number of emergency vacuums to
+ * prevent anti-wraparound
+ * shutdown */
} LVRelState;
@@ -418,6 +423,18 @@ typedef struct LVSavedErrInfo
VacErrPhase phase;
} LVSavedErrInfo;
+/*
+ * Counters and usage data for extended stats tracking.
+ */
+typedef struct LVExtStatCounters
+{
+ TimestampTz starttime;
+ 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);
@@ -487,6 +504,102 @@ 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;
+
+ memset(counters, 0, sizeof(LVExtStatCounters));
+
+ starttime = GetCurrentTimestamp();
+
+ counters->starttime = 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 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;
+
+ /* 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->starttime, 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;
+
+ 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;
+}
/*
@@ -645,6 +758,13 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
BufferUsage startbufferusage = pgBufferUsage;
ErrorContextCallback errcallback;
char **indnames = NULL;
+ LVExtStatCounters extVacCounters;
+ ExtVacReport extVacReport;
+ ExtVacReport allzero;
+
+ /* Initialize vacuum statistics */
+ memset(&allzero, 0, sizeof(ExtVacReport));
+ extVacReport = allzero;
verbose = (params.options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -673,6 +793,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
pgstat_progress_update_param(PROGRESS_VACUUM_STARTED_BY,
PROGRESS_VACUUM_STARTED_BY_MANUAL);
+ 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
@@ -689,6 +811,7 @@ heap_vacuum_rel(Relation rel, const 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;
@@ -797,6 +920,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
vacrel->aggressive = vacuum_get_cutoffs(rel, params, &vacrel->cutoffs);
vacrel->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel);
vacrel->vistest = GlobalVisTestFor(rel);
+ vacrel->wraparound_failsafe_count = 0;
/* Initialize state used to track oldest extant XID/MXID */
vacrel->NewRelfrozenXid = vacrel->cutoffs.OldestXmin;
@@ -951,6 +1075,23 @@ heap_vacuum_rel(Relation rel, const 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.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
+ extVacReport.vm_new_visible_pages = vacrel->vm_new_visible_pages;
+ extVacReport.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
+ extVacReport.tuples_deleted = vacrel->tuples_deleted;
+ extVacReport.tuples_frozen = vacrel->tuples_frozen;
+ extVacReport.recently_dead_tuples = vacrel->recently_dead_tuples;
+ extVacReport.missed_dead_tuples = vacrel->missed_dead_tuples;
+ extVacReport.missed_dead_pages = vacrel->missed_dead_pages;
+ extVacReport.index_vacuum_count = vacrel->num_index_scans;
+ extVacReport.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+
/*
* Report results to the cumulative stats system, too.
*
@@ -965,7 +1106,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
Max(vacrel->new_live_tuples, 0),
vacrel->recently_dead_tuples +
vacrel->missed_dead_tuples,
- starttime);
+ starttime,
+ &extVacReport);
pgstat_progress_end_command();
if (instrument)
@@ -3019,6 +3161,7 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
int64 progress_val[3] = {0, 0, PROGRESS_VACUUM_MODE_FAILSAFE};
VacuumFailsafeActive = true;
+ vacrel->wraparound_failsafe_count++;
/*
* Abandon use of a buffer access strategy to allow use of all of
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index d14588e92ae..3030242d98e 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -92,6 +92,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"
@@ -161,6 +162,15 @@ visibilitymap_clear(Relation rel, BlockNumber heapBlk, Buffer vmbuf, uint8 flags
if (map[mapByte] & mask)
{
+ /*
+ * As part of vacuum stats, track how often all-visible or all-frozen
+ * bits are cleared.
+ */
+ 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 0a0f95f6bb9..ffb407d414f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -727,7 +727,9 @@ CREATE VIEW pg_stat_all_tables AS
pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time,
pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time,
pg_stat_get_total_autoanalyze_time(C.oid) AS total_autoanalyze_time,
- pg_stat_get_stat_reset_time(C.oid) AS stats_reset
+ pg_stat_get_stat_reset_time(C.oid) AS stats_reset,
+ pg_stat_get_rev_all_frozen_pages(C.oid) AS rev_all_frozen_pages,
+ pg_stat_get_rev_all_visible_pages(C.oid) AS rev_all_visible_pages
FROM pg_class C LEFT JOIN
pg_index I ON C.oid = I.indrelid
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -1452,3 +1454,51 @@ REVOKE ALL ON pg_aios FROM PUBLIC;
GRANT SELECT ON pg_aios TO pg_read_all_stats;
REVOKE EXECUTE ON FUNCTION pg_get_aios() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION pg_get_aios() TO pg_read_all_stats;
+--
+-- 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
+ ns.nspname AS schemaname,
+ rel.relname AS relname,
+ stats.relid as relid,
+
+ stats.total_blks_read AS total_blks_read,
+ stats.total_blks_hit AS total_blks_hit,
+ stats.total_blks_dirtied AS total_blks_dirtied,
+ stats.total_blks_written AS total_blks_written,
+
+ stats.rel_blks_read AS rel_blks_read,
+ stats.rel_blks_hit AS rel_blks_hit,
+
+ stats.pages_scanned AS pages_scanned,
+ stats.pages_removed AS pages_removed,
+ stats.vm_new_frozen_pages AS vm_new_frozen_pages,
+ stats.vm_new_visible_pages AS vm_new_visible_pages,
+ stats.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
+ stats.missed_dead_pages AS missed_dead_pages,
+ stats.tuples_deleted AS tuples_deleted,
+ stats.tuples_frozen AS tuples_frozen,
+ stats.recently_dead_tuples AS recently_dead_tuples,
+ stats.missed_dead_tuples AS missed_dead_tuples,
+
+ stats.wraparound_failsafe AS wraparound_failsafe,
+ stats.index_vacuum_count AS index_vacuum_count,
+ stats.wal_records AS wal_records,
+ stats.wal_fpi AS wal_fpi,
+ stats.wal_bytes AS wal_bytes,
+
+ stats.blk_read_time AS blk_read_time,
+ stats.blk_write_time AS blk_write_time,
+
+ stats.delay_time AS delay_time,
+ stats.total_time AS total_time
+
+FROM pg_class rel
+ JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
+ LATERAL pg_stat_get_vacuum_tables(rel.oid) stats
+WHERE rel.relkind = 'r';
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0528d1b6ecb..dd519447387 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -117,6 +117,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);
@@ -2536,6 +2539,7 @@ vacuum_delay_point(bool is_analyze)
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 8a37c08871a..114cd7c31d3 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1054,6 +1054,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_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 55a10c299db..361713479e8 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -47,6 +47,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);
/*
@@ -208,7 +210,7 @@ pgstat_drop_relation(Relation rel)
*/
void
pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
- PgStat_Counter deadtuples, TimestampTz starttime)
+ PgStat_Counter deadtuples, TimestampTz starttime, ExtVacReport * params)
{
PgStat_EntryRef *entry_ref;
PgStatShared_Relation *shtabentry;
@@ -234,6 +236,8 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
tabentry->live_tuples = livetuples;
tabentry->dead_tuples = deadtuples;
+ pgstat_accumulate_extvac_stats(&tabentry->vacuum_ext, params, true);
+
/*
* It is quite possible that a non-aggressive VACUUM ended up skipping
* various pages, however, we'll zero the insert counter here regardless.
@@ -880,6 +884,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 */
@@ -1009,3 +1016,40 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
trans->tuples_deleted = trans->deleted_pre_truncdrop;
}
}
+
+static void
+pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
+ bool accumulate_reltype_specific_info)
+{
+ dst->total_blks_read += src->total_blks_read;
+ dst->total_blks_hit += src->total_blks_hit;
+ dst->total_blks_dirtied += src->total_blks_dirtied;
+ dst->total_blks_written += src->total_blks_written;
+ dst->wal_bytes += src->wal_bytes;
+ dst->wal_fpi += src->wal_fpi;
+ dst->wal_records += src->wal_records;
+ dst->blk_read_time += src->blk_read_time;
+ dst->blk_write_time += src->blk_write_time;
+ dst->delay_time += src->delay_time;
+ dst->total_time += src->total_time;
+
+ 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->vm_new_frozen_pages += src->vm_new_frozen_pages;
+ dst->vm_new_visible_pages += src->vm_new_visible_pages;
+ dst->vm_new_visible_frozen_pages += src->vm_new_visible_frozen_pages;
+ dst->tuples_deleted += src->tuples_deleted;
+ dst->tuples_frozen += src->tuples_frozen;
+ dst->recently_dead_tuples += src->recently_dead_tuples;
+ dst->index_vacuum_count += src->index_vacuum_count;
+ dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
+ dst->missed_dead_pages += src->missed_dead_pages;
+ dst->missed_dead_tuples += src->missed_dead_tuples;
+
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ef6fffe60b9..d7dfda0c1a7 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -106,6 +106,12 @@ PG_STAT_GET_RELENTRY_INT64(tuples_updated)
/* pg_stat_get_vacuum_count */
PG_STAT_GET_RELENTRY_INT64(vacuum_count)
+/* pg_stat_get_rev_frozen_pages */
+PG_STAT_GET_RELENTRY_INT64(rev_all_frozen_pages)
+
+/* pg_stat_get_rev_all_visible_pages */
+PG_STAT_GET_RELENTRY_INT64(rev_all_visible_pages)
+
#define PG_STAT_GET_RELENTRY_FLOAT8(stat) \
Datum \
CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS) \
@@ -2307,3 +2313,83 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objid));
}
+
+
+/*
+ * Get the vacuum statistics for the heap tables.
+ */
+Datum
+pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 26
+
+ Oid relid = PG_GETARG_OID(0);
+ PgStat_StatTabEntry *tabentry;
+ ExtVacReport *extvacuum;
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
+ char buf[256];
+ int i = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tabentry = pgstat_fetch_stat_tabentry(relid);
+
+ if (!tabentry)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
+ else
+ {
+ extvacuum = &(tabentry->vacuum_ext);
+ }
+
+ i = 0;
+
+ values[i++] = ObjectIdGetDatum(relid);
+
+ values[i++] = Int64GetDatum(extvacuum->total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+
+ values[i++] = Int64GetDatum(extvacuum->blks_fetched -
+ extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->blks_hit);
+
+ values[i++] = Int64GetDatum(extvacuum->pages_scanned);
+ values[i++] = Int64GetDatum(extvacuum->pages_removed);
+ values[i++] = Int64GetDatum(extvacuum->vm_new_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->vm_new_visible_pages);
+ values[i++] = Int64GetDatum(extvacuum->vm_new_visible_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->missed_dead_pages);
+ values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+ values[i++] = Int64GetDatum(extvacuum->tuples_frozen);
+ values[i++] = Int64GetDatum(extvacuum->recently_dead_tuples);
+ values[i++] = Int64GetDatum(extvacuum->missed_dead_tuples);
+ values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+ values[i++] = Int64GetDatum(extvacuum->index_vacuum_count);
+
+ values[i++] = Int64GetDatum(extvacuum->wal_records);
+ values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+ /* Convert to numeric, like pg_stat_statements */
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ values[i++] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ values[i++] = Float8GetDatum(extvacuum->blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->delay_time);
+ values[i++] = Float8GetDatum(extvacuum->total_time);
+
+ Assert(i == PG_STAT_GET_VACUUM_TABLES_STATS_COLS);
+
+ /* Returns the record as Datum */
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..867638fe74b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -669,6 +669,7 @@
#track_wal_io_timing = off
#track_functions = none # none, pl, all
#stats_fetch_consistency = cache # cache, none, snapshot
+#track_vacuum_statistics = off
# - Monitoring -
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fd9448ec7b9..915a5a7822f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12612,4 +12612,22 @@
proargnames => '{pid,io_id,io_generation,state,operation,off,length,target,handle_data_len,raw_result,result,target_desc,f_sync,f_localmem,f_buffered}',
prosrc => 'pg_get_aios' },
+{ oid => '8001',
+ descr => 'pg_stat_get_vacuum_tables returns vacuum stats values for table',
+ proname => 'pg_stat_get_vacuum_tables', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
+ proretset => 't',
+ proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int4,int8,int8,int8,numeric,float8,float8,float8,float8}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,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,vm_new_frozen_pages,vm_new_visible_pages,vm_new_visible_frozen_pages,missed_dead_pages,tuples_deleted,tuples_frozen,recently_dead_tuples,missed_dead_tuples,wraparound_failsafe,index_vacuum_count,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time}',
+ prosrc => 'pg_stat_get_vacuum_tables' },
+
+ { oid => '8002', descr => 'statistics: number of times the all-visible pages in the visibility map was removed for pages of table',
+ proname => 'pg_stat_get_rev_all_visible_pages', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_rev_all_visible_pages' },
+ { oid => '8003', descr => 'statistics: number of times the all-frozen pages in the visibility map was removed for pages of table',
+ proname => 'pg_stat_get_rev_all_frozen_pages', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_rev_all_frozen_pages' },
]
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 1f3290c7fbf..6b997bc7fb1 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -332,6 +332,7 @@ extern PGDLLIMPORT double vacuum_max_eager_freeze_failure_rate;
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 6714363144a..46d12fa3bd0 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -114,6 +114,66 @@ typedef struct PgStat_BackendSubEntry
PgStat_Counter conflict_count[CONFLICT_NUM_TYPES];
} 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
+{
+ /*
+ * number of blocks missed, hit, dirtied and written during a vacuum of
+ * specific relation
+ */
+ int64 total_blks_read;
+ int64 total_blks_hit;
+ int64 total_blks_dirtied;
+ int64 total_blks_written;
+
+ /*
+ * blocks missed and hit for just the heap during a vacuum of specific
+ * relation
+ */
+ int64 blks_fetched;
+ int64 blks_hit;
+
+ /* Vacuum WAL usage stats */
+ int64 wal_records; /* wal usage: number of WAL records */
+ int64 wal_fpi; /* wal usage: number of WAL full page images
+ * produced */
+ uint64 wal_bytes; /* wal usage: size of WAL records produced */
+
+ /* Time stats. */
+ double blk_read_time; /* time spent reading pages, in msec */
+ double blk_write_time; /* time spent writing pages, in msec */
+ double delay_time; /* how long vacuum slept in vacuum delay
+ * point, in msec */
+ double total_time; /* total time of a vacuum operation, in msec */
+
+ int64 pages_scanned; /* heap pages examined (not skipped by VM) */
+ int64 pages_removed; /* heap pages removed by vacuum "truncation" */
+ int64 vm_new_frozen_pages; /* pages marked in VM as frozen */
+ int64 vm_new_visible_pages; /* pages marked in VM as all-visible */
+ int64 vm_new_visible_frozen_pages; /* pages marked in VM as
+ * all-visible and frozen */
+ int64 missed_dead_tuples; /* tuples not pruned by vacuum due to
+ * failure to get a cleanup lock */
+ int64 missed_dead_pages; /* pages with missed dead tuples */
+ int64 tuples_deleted; /* tuples deleted by vacuum */
+ int64 tuples_frozen; /* tuples frozen up by vacuum */
+ int64 recently_dead_tuples; /* deleted tuples that are still
+ * visible to some transaction */
+ int64 index_vacuum_count; /* the number of index vacuumings */
+ int32 wraparound_failsafe_count; /* number of emergency vacuums to
+ * prevent anti-wraparound
+ * shutdown */
+} ExtVacReport;
+
/* ----------
* PgStat_TableCounts The actual per-table counts kept by a backend
*
@@ -156,6 +216,15 @@ 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;
/* ----------
@@ -214,7 +283,7 @@ typedef struct PgStat_TableXactStatus
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BCBB
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BCBC
typedef struct PgStat_ArchiverStats
{
@@ -378,6 +447,8 @@ typedef struct PgStat_StatDBEntry
PgStat_Counter parallel_workers_launched;
TimestampTz stat_reset_timestamp;
+
+ ExtVacReport vacuum_ext; /* extended vacuum statistics */
} PgStat_StatDBEntry;
typedef struct PgStat_StatFuncEntry
@@ -461,8 +532,12 @@ typedef struct PgStat_StatTabEntry
PgStat_Counter total_autovacuum_time;
PgStat_Counter total_analyze_time;
PgStat_Counter total_autoanalyze_time;
-
TimestampTz stat_reset_time;
+
+ PgStat_Counter rev_all_visible_pages;
+ PgStat_Counter rev_all_frozen_pages;
+
+ ExtVacReport vacuum_ext;
} PgStat_StatTabEntry;
/* ------
@@ -671,7 +746,7 @@ extern void pgstat_unlink_relation(Relation rel);
extern void pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
PgStat_Counter deadtuples,
- TimestampTz starttime);
+ TimestampTz starttime, ExtVacReport * params);
extern void pgstat_report_analyze(Relation rel,
PgStat_Counter livetuples, PgStat_Counter deadtuples,
bool resetcounter, TimestampTz starttime);
@@ -722,6 +797,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);
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..87f7e40b4a6
--- /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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
+--------------------------+--------------+--------------------+------------------+-----------------+-------------
+test_vacuum_stat_isolation| 0| 0| 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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
+--------------------------+--------------+--------------------+------------------+-----------------+-------------
+test_vacuum_stat_isolation| 0| 100| 0| 0| 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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
+--------------------------+--------------+--------------------+------------------+-----------------+-------------
+test_vacuum_stat_isolation| 100| 100| 0| 0| 101
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f2e067b1fbc..1c231418706 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -98,6 +98,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..5893d89573d
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -0,0 +1,53 @@
+# Test for checking recently_dead_tuples, tuples_deleted and frozen tuples in pg_stat_vacuum_tables.
+# recently_dead_tuples values are counted when vacuum hasn't cleared tuples because they were deleted recently.
+# recently_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;
+ SET track_vacuum_statistics TO 'on';
+}
+
+teardown
+{
+ DROP TABLE test_vacuum_stat_isolation CASCADE;
+ RESET track_io_timing;
+ RESET track_vacuum_statistics;
+}
+
+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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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
\ No newline at end of file
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
new file mode 100644
index 00000000000..7e25a3fe63f
--- /dev/null
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -0,0 +1,571 @@
+# Copyright (c) 2025 PostgreSQL Global Development Group
+# Test cumulative vacuum stats system using TAP
+#
+# This test validates the accuracy and behavior of cumulative vacuum statistics
+# across tables using:
+#
+# • pg_stat_vacuum_tables
+#
+# A polling helper function repeatedly checks the stats views until expected
+# deltas appear or a configurable timeout expires. This guarantees that
+# stats-collector propagation delays do not lead to flaky test behavior.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+#------------------------------------------------------------------------------
+# Test harness setup
+#------------------------------------------------------------------------------
+
+my $node = PostgreSQL::Test::Cluster->new('stat_vacuum');
+$node->init;
+
+# Configure the server logging level for the test
+$node->append_conf('postgresql.conf', q{
+ log_min_messages = notice
+});
+
+my $stderr;
+my $base_stats;
+my $wals;
+my $ibase_stats;
+my $iwals;
+
+$node->start(
+ '>' => \$base_stats,
+ '2>' => \$stderr
+);
+
+#------------------------------------------------------------------------------
+# Database creation and initialization
+#------------------------------------------------------------------------------
+
+$node->safe_psql('postgres', q{
+ CREATE DATABASE statistic_vacuum_database_regression;
+});
+# Main test database name and number of rows to insert
+my $dbname = 'statistic_vacuum_database_regression';
+my $size_tab = 1000;
+
+# Enable required session settings and force the stats collector to flush next
+$node->safe_psql($dbname, q{
+ SET track_functions = 'all';
+ SELECT pg_stat_force_next_flush();
+});
+
+#------------------------------------------------------------------------------
+# Create test table and populate it
+#------------------------------------------------------------------------------
+
+$node->safe_psql(
+ $dbname,
+ "CREATE TABLE vestat (x int)
+ WITH (autovacuum_enabled = off, fillfactor = 10);
+ INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ ANALYZE vestat;"
+);
+
+#------------------------------------------------------------------------------
+# Timing parameters for polling loops
+#------------------------------------------------------------------------------
+
+my $timeout = 30; # overall wait timeout in seconds
+my $interval = 0.015; # poll interval in seconds (15 ms)
+my $start_time = time();
+my $updated = 0;
+
+#------------------------------------------------------------------------------
+# wait_for_vacuum_stats
+#
+# Polls pg_stat_vacuum_tables until the table-level counters exceed
+# the provided baselines, or until the configured timeout elapses.
+#
+# Expected named args (baseline values):
+# tab_tuples_deleted
+# tab_wal_records
+#
+# Returns: 1 if the condition is met before timeout, 0 otherwise.
+#------------------------------------------------------------------------------
+
+sub wait_for_vacuum_stats {
+ my (%args) = @_;
+ my $tab_tuples_deleted = $args{tab_tuples_deleted} or 0;
+ my $tab_wal_records = $args{tab_wal_records} or 0;
+
+ my $start = time();
+ while ((time() - $start) < $timeout) {
+
+ my $result_query = $node->safe_psql(
+ $dbname,
+ "VACUUM vestat;
+ SELECT tuples_deleted > $tab_tuples_deleted AND wal_records > $tab_wal_records
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ return 1 if ($result_query eq 't');
+
+ sleep($interval);
+ }
+
+ return 0;
+}
+
+#------------------------------------------------------------------------------
+# Variables to hold vacuum-stat snapshots for later comparisons
+#------------------------------------------------------------------------------
+
+my $pages_frozen = 0;
+my $tuples_deleted = 0;
+my $pages_scanned = 0;
+my $pages_removed = 0;
+my $wal_records = 0;
+my $wal_bytes = 0;
+my $wal_fpi = 0;
+
+my $pages_frozen_prev = 0;
+my $tuples_deleted_prev = 0;
+my $pages_scanned_prev = 0;
+my $pages_removed_prev = 0;
+my $wal_records_prev = 0;
+my $wal_bytes_prev = 0;
+my $wal_fpi_prev = 0;
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats
+#
+# Reads current values of relevant vacuum counters for the test table,
+# storing them in package variables for subsequent comparisons.
+#------------------------------------------------------------------------------
+
+sub fetch_vacuum_stats {
+ # fetch actual base vacuum statistics
+ my $base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT vm_new_frozen_pages, tuples_deleted, pages_scanned, pages_removed, wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ $base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
+ ($pages_frozen, $tuples_deleted, $pages_scanned, $pages_removed, $wal_records, $wal_bytes, $wal_fpi)
+ = split /\s+/, $base_statistics;
+}
+
+#------------------------------------------------------------------------------
+# save_vacuum_stats
+#
+# Save current values (previously fetched by fetch_vacuum_stats) so that we
+# later fetch new values and compare them.
+#------------------------------------------------------------------------------
+sub save_vacuum_stats {
+ $pages_frozen_prev = $pages_frozen;
+ $tuples_deleted_prev = $tuples_deleted;
+ $pages_scanned_prev = $pages_scanned;
+ $pages_removed_prev = $pages_removed;
+ $wal_records_prev = $wal_records;
+ $wal_bytes_prev = $wal_bytes;
+ $wal_fpi_prev = $wal_fpi;
+}
+
+#------------------------------------------------------------------------------
+# print_vacuum_stats_on_error
+#
+# Print values in case of an error
+#------------------------------------------------------------------------------
+sub print_vacuum_stats_on_error {
+ diag(
+ "Statistics in the failed test\n" .
+ "Table statistics:\n" .
+ " Before test:\n" .
+ " pages_frozen = $pages_frozen_prev\n" .
+ " tuples_deleted = $tuples_deleted_prev\n" .
+ " pages_scanned = $pages_scanned_prev\n" .
+ " pages_removed = $pages_removed_prev\n" .
+ " wal_records = $wal_records_prev\n" .
+ " wal_bytes = $wal_bytes_prev\n" .
+ " wal_fpi = $wal_fpi_prev\n" .
+ " After test:\n" .
+ " pages_frozen = $pages_frozen\n" .
+ " tuples_deleted = $tuples_deleted\n" .
+ " pages_scanned = $pages_scanned\n" .
+ " pages_removed = $pages_removed\n" .
+ " wal_records = $wal_records\n" .
+ " wal_bytes = $wal_bytes\n" .
+ " wal_fpi = $wal_fpi\n"
+ );
+};
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats during mismatch
+#
+# Print current values and old values of relevant vacuum counters for the test
+# table, storing them in package variables for subsequent comparisons.
+#------------------------------------------------------------------------------
+
+sub fetch_error_base_tab_vacuum_statistics {
+
+ # fetch actual base vacuum statistics
+ my $base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT vm_new_frozen_pages, tuples_deleted, pages_scanned, pages_removed
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+ $base_statistics =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_pages_frozen, $cur_tuples_deleted, $cur_pages_scanned, $cur_pages_removed) = split /\s+/, $base_statistics;
+
+ diag(
+ "BASE STATS MISMATCH FOR TABLE:\n" .
+ " Baseline:\n" .
+ " pages_frozen = $pages_frozen\n" .
+ " tuples_deleted = $tuples_deleted\n" .
+ " pages_scanned = $pages_scanned\n" .
+ " pages_removed = $pages_removed\n" .
+ " Current:\n" .
+ " pages_frozen = $cur_pages_frozen\n" .
+ " tuples_deleted = $cur_tuples_deleted\n" .
+ " pages_scanned = $cur_pages_scanned\n" .
+ " pages_removed = $cur_pages_removed\n"
+ );
+}
+
+sub fetch_error_wal_tab_vacuum_statistics {
+
+ my $wal_raw = $node->safe_psql(
+ $dbname,
+ "SELECT wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ $wal_raw =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_wal_rec, $cur_wal_bytes, $cur_wal_fpi) = split /\s+/, $wal_raw;
+
+ diag(
+ "WAL STATS MISMATCH FOR TABLE:\n" .
+ " Baseline:\n" .
+ " wal_records = $wal_records\n" .
+ " wal_bytes = $wal_bytes\n" .
+ " wal_fpi = $wal_fpi\n" .
+ " Current:\n" .
+ " wal_records = $cur_wal_rec\n" .
+ " wal_bytes = $cur_wal_bytes\n" .
+ " wal_fpi = $cur_wal_fpi\n"
+ );
+}
+
+#------------------------------------------------------------------------------
+# Test 1: Delete half the rows, run VACUUM, and wait for stats to advance
+#------------------------------------------------------------------------------
+subtest 'Test 1: Delete half the rows, run VACUUM, and wait for stats to advance' => sub
+{
+
+$node->safe_psql($dbname, "DELETE FROM vestat WHERE x % 2 = 0;");
+$node->safe_psql($dbname, "VACUUM vestat;");
+
+# Poll the stats view until expected deltas appear or timeout
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => 0,
+ tab_wal_records => 0
+);
+ok($updated, 'vacuum stats updated after vacuuming half-deleted table (tuples_deleted and wal_fpi advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds after vacuuming half-deleted table";
+
+#------------------------------------------------------------------------------
+# Check statistics after half-table delete
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted > $tuples_deleted_prev, 'table tuples_deleted has increased');
+ok($pages_scanned > $pages_scanned_prev, 'table pages_scanned has increased');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 2: Delete all rows, run VACUUM, and wait for stats to advance
+#------------------------------------------------------------------------------
+subtest 'Test 2: Delete all rows, run VACUUM, and wait for stats to advance' => sub
+{
+
+$node->safe_psql($dbname, "DELETE FROM vestat;");
+$node->safe_psql($dbname, "VACUUM vestat;");
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => $tuples_deleted_prev,
+ tab_wal_records => $wal_records_prev,
+);
+
+ok($updated, 'vacuum stats updated after vacuuming all-deleted table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds after vacuuming all-deleted table";
+
+#------------------------------------------------------------------------------
+# Check statistics after full delete
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted > $tuples_deleted_prev, 'table tuples_deleted has increased');
+ok($pages_scanned > $pages_scanned_prev, 'table pages_scanned has increased');
+ok($pages_removed > $pages_removed_prev, 'table pages_removed has increased');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 3: Test VACUUM FULL — it should not report to the stats collector
+#------------------------------------------------------------------------------
+subtest 'Test 3: Test VACUUM FULL — it should not report to the stats collector' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ CHECKPOINT;
+ DELETE FROM vestat;
+ VACUUM FULL vestat;"
+);
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted == $tuples_deleted_prev, 'table tuples_deleted stay the same');
+ok($pages_scanned == $pages_scanned_prev, 'table pages_scanned stay the same');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records == $wal_records_prev, 'table wal_records stay the same');
+ok($wal_bytes == $wal_bytes_prev, 'table wal_bytes stay the same');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 4: Update table, checkpoint, and VACUUM to provoke WAL/FPI accounting
+#------------------------------------------------------------------------------
+subtest 'Test 4: Update table, checkpoint, and VACUUM to provoke WAL/FPI accounting' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ CHECKPOINT;
+ UPDATE vestat SET x = x + 1000;
+ VACUUM vestat;"
+);
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => $tuples_deleted,
+ tab_wal_records => $wal_records,
+);
+
+ok($updated, 'vacuum stats updated after updating tuples in the table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Verify statistics after updating tuples and vacuuming
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted > $tuples_deleted_prev, 'table tuples_deleted has increased');
+ok($pages_scanned > $pages_scanned_prev, 'table pages_scanned has increased');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 5: Update table, trancate and vacuuming
+#------------------------------------------------------------------------------
+subtest 'Test 5: Update table, trancate and vacuuming' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ UPDATE vestat SET x = x + 1000;"
+);
+$node->safe_psql($dbname, "TRUNCATE vestat;");
+$node->safe_psql($dbname, "CHECKPOINT;");
+$node->safe_psql($dbname, "VACUUM vestat;");
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => 0,
+ tab_wal_records => $wal_records_prev
+);
+
+ok($updated, 'vacuum stats updated after updating tuples and trancation in the table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Verify statistics after updating full table, vacuum and trancation
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted == $tuples_deleted_prev, 'table tuples_deleted stay the same');
+ok($pages_scanned == $pages_scanned_prev, 'table pages_scanned stay the same');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 6: Delete all tuples from table, trancate, and vacuuming
+#------------------------------------------------------------------------------
+subtest 'Test 6: Delete all tuples from table, trancate, and vacuuming' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ DELETE FROM vestat;
+ TRUNCATE vestat;
+ CHECKPOINT;
+ VACUUM vestat;"
+);
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => 0,
+ tab_wal_records => $wal_records
+);
+
+ok($updated, 'vacuum stats updated after deleting all tuples and trancation in the table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Verify statistics after table vacuum and trancation
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted == $tuples_deleted_prev, 'table tuples_deleted stay the same');
+ok($pages_scanned == $pages_scanned_prev, 'table pages_scanned stay the same');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#-------------------------------------------------------------------------------------------------------
+# Test 8: Check if we return single vacuum statistics for particular relation from the current database
+#-------------------------------------------------------------------------------------------------------
+
+my $dboid = $node->safe_psql(
+ $dbname,
+ "SELECT oid FROM pg_database WHERE datname = current_database();"
+);
+
+my $reloid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'vestat';
+ }
+);
+
+# Check if we can get vacuum statistics of particular heap elation in the current database
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 1 FROM pg_stat_get_vacuum_tables($reloid);"
+);
+ok($base_stats eq 't', 'heap vacuum stats return from the current relation and database as expected');
+
+#------------------------------------------------------------------------------
+# Test 9: Check relation-level vacuum statistics from another database
+#------------------------------------------------------------------------------
+
+$base_stats = $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) = 0
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+);
+ok($base_stats eq 't', 'check the printing heap vacuum extended statistics from another database are not available');
+
+$reloid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'pg_shdepend';
+ }
+);
+
+# Check if we can get vacuum statistics for cluster relations (dbid = 0)
+$base_stats = $node->safe_psql(
+ $dbname,
+ qq{
+ SELECT count(*) = 1
+ FROM pg_stat_get_vacuum_tables($reloid);
+ }
+);
+
+is($base_stats, 't', 'vacuum stats for common heap objects available');
+
+#------------------------------------------------------------------------------
+# Test 11: Cleanup checks: ensure functions return empty sets for OID = 0
+#------------------------------------------------------------------------------
+
+$node->safe_psql($dbname, q{
+ DROP TABLE vestat CASCADE;
+ VACUUM;
+});
+
+# Check that we don't print vacuum statistics for deleted objects
+$base_stats = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT COUNT(*) = 0
+ FROM pg_stat_vacuum_tables WHERE relid = 0;
+ }
+);
+ok($base_stats eq 't', 'pg_stat_vacuum_tables correctly returns no rows for OID = 0');
+
+$node->safe_psql('postgres',
+ "DROP DATABASE $dbname;"
+);
+
+$node->stop;
+
+done_testing();
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
new file mode 100644
index 00000000000..a9b5d6cb739
--- /dev/null
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -0,0 +1,395 @@
+# Copyright (c) 2025 PostgreSQL Global Development Group
+#
+# Test cumulative vacuum stats system using TAP
+#
+# In short, this test validates the correctness and stability of cumulative
+# vacuum statistics accounting around freezing, visibility, and revision
+# tracking across multiple VACUUMs and backend operations.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+#------------------------------------------------------------------------------
+# Test cluster setup
+#------------------------------------------------------------------------------
+
+my $node = PostgreSQL::Test::Cluster->new('ext_stat_vacuum');
+$node->init;
+
+# Configure the server for aggressive freezing behavior used by the test
+# These settings ensure that VACUUM always freezes pages aggressively:
+# - vacuum_freeze_min_age = 0: freeze tuples as soon as possible (no age requirement)
+# - vacuum_freeze_table_age = 0: always perform aggressive scan (scan all pages)
+# - vacuum_multixact_freeze_min_age = 0: freeze multixacts as soon as possible
+# - vacuum_multixact_freeze_table_age = 0: always perform aggressive scan for multixacts
+# - vacuum_max_eager_freeze_failure_rate = 1.0: enable aggressive eager scanning (100% of pages)
+# - vacuum_failsafe_age = 0: disable failsafe (for testing)
+# - vacuum_multixact_failsafe_age = 0: disable multixact failsafe (for testing)
+$node->append_conf('postgresql.conf', q{
+ log_min_messages = notice
+ vacuum_freeze_min_age = 0
+ vacuum_freeze_table_age = 0
+ vacuum_multixact_freeze_min_age = 0
+ vacuum_multixact_freeze_table_age = 0
+ vacuum_max_eager_freeze_failure_rate = 1.0
+ vacuum_failsafe_age = 0
+ vacuum_multixact_failsafe_age = 0
+});
+
+$node->start();
+
+#------------------------------------------------------------------------------
+# Database creation and initialization
+#------------------------------------------------------------------------------
+
+$node->safe_psql('postgres', q{
+ CREATE DATABASE statistic_vacuum_database_regression;
+});
+
+# Main test database name
+my $dbname = 'statistic_vacuum_database_regression';
+
+# Enable necessary settings and force the stats collector to flush next
+$node->safe_psql($dbname, q{
+ SET track_functions = 'all';
+ SELECT pg_stat_force_next_flush();
+});
+
+#------------------------------------------------------------------------------
+# Timing parameters for polling loops
+#------------------------------------------------------------------------------
+
+my $timeout = 30; # overall wait timeout in seconds
+my $interval = 0.015; # poll interval in seconds (15 ms)
+my $start_time = time();
+my $updated = 0;
+
+# wait_for_vacuum_stats
+#
+# Polls pg_stat_vacuum_tables until the named columns exceed the provided
+# baseline values or until timeout. Callers should pass:
+#
+# tab_frozen_column => 'vm_new_frozen_pages' # column name (string) or 'rev_all_frozen_pages'
+# tab_visible_column => 'vm_new_visible_pages' # column name (string) or 'rev_all_visible_pages'
+# tab_all_frozen_pages_count => 0 # baseline numeric
+# tab_all_visible_pages_count => 0 # baseline numeric
+# run_vacuum => 0 or 1 # if true, run vacuum_sql before polling
+#
+# Returns: 1 if the condition is met before timeout, 0 otherwise.
+sub wait_for_vacuum_stats {
+ my (%args) = @_;
+
+ my $tab_frozen_column = $args{tab_frozen_column};
+ my $tab_visible_column = $args{tab_visible_column};
+ my $tab_all_frozen_pages_count = $args{tab_all_frozen_pages_count};
+ my $tab_all_visible_pages_count = $args{tab_all_visible_pages_count};
+ my $run_vacuum = $args{run_vacuum} ? 1 : 0;
+ my $result_query;
+
+ my $start = time();
+ my $sql;
+
+ while ((time() - $start) < $timeout) {
+
+ if ($run_vacuum) {
+ $node->safe_psql($dbname, 'VACUUM (FREEZE, VERBOSE) vestat');
+ $sql = "
+ SELECT ($tab_frozen_column > $tab_all_frozen_pages_count AND
+ $tab_visible_column > $tab_all_visible_pages_count)
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat'";
+ }
+ else {
+ $sql = "
+ SELECT (pg_stat_get_rev_all_frozen_pages(c.oid) > $tab_all_frozen_pages_count AND
+ pg_stat_get_rev_all_visible_pages(c.oid) > $tab_all_visible_pages_count)
+ FROM pg_class c
+ WHERE relname = 'vestat'";
+ }
+
+ $result_query = $node->safe_psql($dbname, $sql);
+
+ return 1 if (defined $result_query && $result_query eq 't');
+
+ # sub-second sleep
+ sleep($interval);
+ }
+
+ return 0;
+}
+
+#------------------------------------------------------------------------------
+# Variables to hold vacuum statistics snapshots for comparisons
+#------------------------------------------------------------------------------
+
+my $vm_new_frozen_pages;
+my $vm_new_visible_pages;
+
+my $rev_all_frozen_pages;
+my $rev_all_visible_pages;
+
+my $res;
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats
+#
+# Loads current values of the relevant vacuum counters for the test table
+# into the package-level variables above so tests can compare later.
+#------------------------------------------------------------------------------
+
+sub fetch_vacuum_stats {
+ # fetch actual base vacuum statistics
+ $vm_new_frozen_pages = $node->safe_psql(
+ $dbname,
+ "SELECT vt.vm_new_frozen_pages
+ FROM pg_stat_vacuum_tables vt
+ WHERE vt.relname = 'vestat';"
+ );
+
+ $vm_new_visible_pages = $node->safe_psql(
+ $dbname,
+ "SELECT vt.vm_new_visible_pages
+ FROM pg_stat_vacuum_tables vt
+ WHERE vt.relname = 'vestat';"
+ );
+
+ $rev_all_frozen_pages = $node->safe_psql(
+ $dbname,
+ "SELECT pg_stat_get_rev_all_frozen_pages(c.oid)
+ FROM pg_class c
+ WHERE c.relname = 'vestat';"
+ );
+
+ $rev_all_visible_pages = $node->safe_psql(
+ $dbname,
+ "SELECT pg_stat_get_rev_all_visible_pages(c.oid)
+ FROM pg_class c
+ WHERE c.relname = 'vestat';"
+ );
+}
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats during mismatch
+#
+# Print current values and old values of relevant vacuum counters for the test
+# table, storing them in package variables for subsequent comparisons.
+#------------------------------------------------------------------------------
+
+sub fetch_error_tab_vacuum_statistics {
+ my (%args) = @_;
+
+ # Validate presence of required args (allow 0 as valid numeric baseline)
+ die "tab_column required"
+ unless exists $args{tab_column} && defined $args{tab_column};
+ die "tab_value required"
+ unless exists $args{tab_value};
+
+ my $tab_column = $args{tab_column};
+ my $tab_value = $args{tab_value};
+
+ # fetch actual base vacuum statistics
+ my $cur_value = $node->safe_psql(
+ $dbname,
+ "SELECT $tab_column
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ diag("MISMATCH FOR $tab_column: the current value is $cur_value, while it should be $tab_value");
+}
+
+#------------------------------------------------------------------------------
+# Test 1: Create test table, populate it and run an initial vacuum to force freezing
+#------------------------------------------------------------------------------
+
+$node->safe_psql($dbname, q{
+ 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, 1000) AS g(x);
+ VACUUM (FREEZE, VERBOSE) vestat;
+});
+
+# Poll the stats view until the expected deltas appear or timeout.
+# We do not expect rev_all_* counters to change here, so we pass -1 for them.
+$updated = wait_for_vacuum_stats(
+ tab_frozen_column => 'vm_new_frozen_pages',
+ tab_visible_column => 'vm_new_visible_pages',
+ tab_all_frozen_pages_count => 0,
+ tab_all_visible_pages_count => 0,
+ run_vacuum => 1,
+);
+
+ok($updated,
+ 'vacuum stats updated after vacuuming the table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
+ or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
+
+#------------------------------------------------------------------------------
+# Snapshot current statistics for later comparison
+#------------------------------------------------------------------------------
+
+fetch_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Verify initial statistics after vacuum
+#------------------------------------------------------------------------------
+
+$res = $node->safe_psql($dbname, q{
+ SELECT vm_new_frozen_pages > 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+});
+ok($res eq 't', 'vacuum froze some pages, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
+
+$res = $node->safe_psql($dbname, q{
+ SELECT vm_new_visible_pages > 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+});
+ok($res eq 't', 'vacuum marked pages all-visible, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value =>$vm_new_visible_pages,);
+
+$res = $node->safe_psql($dbname, q{
+ SELECT pg_stat_get_rev_all_frozen_pages(c.oid) = 0
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';
+});
+ok($res eq 't', 'vacuum did not increase frozen-page revision count, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_frozen_pages', tab_value => 0,);
+
+$res = $node->safe_psql($dbname, q{
+ SELECT pg_stat_get_rev_all_visible_pages(c.oid) = 0
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';
+});
+ok($res eq 't', 'vacuum did not increase visible-page revision count, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_visible_pages', tab_value => 0,);
+
+#------------------------------------------------------------------------------
+# Test 2: Trigger backend updates
+# Backend activity should reset per-page visibility/freeze marks and increment revision counters
+#------------------------------------------------------------------------------
+$node->safe_psql($dbname, q{
+ UPDATE vestat SET x = x + 1001;
+});
+
+# Poll until stats update or timeout.
+# We do not expect vm_new_frozen_pages or vm_new_visible_pages to change here,
+# so we pass -1 for those counters.
+$updated = wait_for_vacuum_stats(
+ tab_frozen_column => 'rev_all_frozen_pages',
+ tab_visible_column => 'rev_all_visible_pages',
+ tab_all_frozen_pages_count => 0,
+ tab_all_visible_pages_count => 0,
+ run_vacuum => 0,
+);
+ok($updated,
+ 'vacuum stats updated after backend tuple updates (rev_all_frozen_pages and rev_all_visible_pages advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Check updated statistics after backend activity
+#------------------------------------------------------------------------------
+
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_frozen_pages = $vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity did not increase the frozen-page count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_visible_pages = $vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity did not increase the all-visible page count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value => $vm_new_visible_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_frozen_pages(c.oid) > $rev_all_frozen_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity increased frozen-page revision count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_frozen_pages', tab_value => $rev_all_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_visible_pages(c.oid) > $rev_all_visible_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity increased visible-page revision count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_visible_pages', tab_value => $rev_all_visible_pages,);
+
+#------------------------------------------------------------------------------
+# Update saved snapshots
+#------------------------------------------------------------------------------
+
+fetch_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 3: Force another vacuum after backend modifications - vacuum should restore freeze/visibility
+#------------------------------------------------------------------------------
+
+$node->safe_psql($dbname, q{ VACUUM (FREEZE, VERBOSE) vestat; });
+
+# Poll until stats update or timeout.
+# We pass current snapshot values for vm_new_frozen_pages/vm_new_visible_pages and expect rev counters unchanged.
+$updated = wait_for_vacuum_stats(
+ tab_frozen_column => 'vm_new_frozen_pages',
+ tab_visible_column => 'vm_new_visible_pages',
+ tab_all_frozen_pages_count => $vm_new_frozen_pages,
+ tab_all_visible_pages_count => $vm_new_visible_pages,
+ run_vacuum => 1,
+);
+
+ok($updated,
+ 'vacuum stats updated after vacuuming the all-updated table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
+ or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
+
+#------------------------------------------------------------------------------
+# Verify statistics after final vacuum
+# Check updated stats after backend work
+#------------------------------------------------------------------------------
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_frozen_pages > $vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum froze some pages after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_visible_pages > $vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum marked pages all-visible after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value => $vm_new_visible_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_frozen_pages(c.oid) = $rev_all_frozen_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum did not increase frozen-page revision count after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_frozen_pages', tab_value => $rev_all_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_visible_pages(c.oid) = $rev_all_visible_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum did not increase visible-page revision count after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_visible_pages', tab_value => $rev_all_visible_pages,);
+
+#------------------------------------------------------------------------------
+# Cleanup
+#------------------------------------------------------------------------------
+
+$node->safe_psql('postgres', q{
+ DROP DATABASE statistic_vacuum_database_regression;
+});
+
+$node->stop;
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 4286c266e17..e4a77878beb 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1844,7 +1844,9 @@ pg_stat_all_tables| SELECT c.oid AS relid,
pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time,
pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time,
pg_stat_get_total_autoanalyze_time(c.oid) AS total_autoanalyze_time,
- pg_stat_get_stat_reset_time(c.oid) AS stats_reset
+ pg_stat_get_stat_reset_time(c.oid) AS stats_reset,
+ pg_stat_get_rev_all_frozen_pages(c.oid) AS rev_all_frozen_pages,
+ pg_stat_get_rev_all_visible_pages(c.oid) AS rev_all_visible_pages
FROM ((pg_class c
LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2266,7 +2268,9 @@ pg_stat_sys_tables| SELECT relid,
total_autovacuum_time,
total_analyze_time,
total_autoanalyze_time,
- stats_reset
+ stats_reset,
+ rev_all_frozen_pages,
+ rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
pg_stat_user_functions| SELECT p.oid AS funcid,
@@ -2321,9 +2325,43 @@ pg_stat_user_tables| SELECT relid,
total_autovacuum_time,
total_analyze_time,
total_autoanalyze_time,
- stats_reset
+ stats_reset,
+ rev_all_frozen_pages,
+ rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
+ rel.relname,
+ stats.relid,
+ stats.total_blks_read,
+ stats.total_blks_hit,
+ stats.total_blks_dirtied,
+ stats.total_blks_written,
+ stats.rel_blks_read,
+ stats.rel_blks_hit,
+ stats.pages_scanned,
+ stats.pages_removed,
+ stats.vm_new_frozen_pages,
+ stats.vm_new_visible_pages,
+ stats.vm_new_visible_frozen_pages,
+ stats.missed_dead_pages,
+ stats.tuples_deleted,
+ stats.tuples_frozen,
+ stats.recently_dead_tuples,
+ stats.missed_dead_tuples,
+ stats.wraparound_failsafe,
+ stats.index_vacuum_count,
+ stats.wal_records,
+ stats.wal_fpi,
+ stats.wal_bytes,
+ stats.blk_read_time,
+ stats.blk_write_time,
+ stats.delay_time,
+ stats.total_time
+ FROM (pg_class rel
+ JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
+ LATERAL pg_stat_get_vacuum_tables(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (rel.relkind = 'r'::"char");
pg_stat_wal| SELECT wal_records,
wal_fpi,
wal_bytes,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 905f9bca959..62f2ac11659 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -139,4 +139,4 @@ test: fast_default
# run tablespace test at the end because it drops the tablespace created during
# setup that other tests may use.
-test: tablespace
+test: tablespace
\ No newline at end of file
--
2.39.5 (Apple Git-154)
From d09c2f688fc8776b239a39f0cd9cda5488dba812 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Tue, 9 Dec 2025 10:56:54 +0300
Subject: [PATCH 2/5] Machinery for grabbing an extended vacuum statistics on
index relations.
They are gathered separatelly from table statistics.
As for tables, we gather vacuum shared buffers statistics for index relations like
value of total_blks_hit, total_blks_read, total_blks_dirtied, wal statistics, io time
during flushing buffer pages to disk, delay and total time.
Due to the fact that such statistics are common as for tables, as for indexes we
set them in the union ExtVacReport structure. We only added some determination 'type'
field to highlight what kind belong to these statistics: PGSTAT_EXTVAC_TABLE or
PGSTAT_EXTVAC_INDEX. Generally, PGSTAT_EXTVAC_INVALID type leads to wrong code process.
Some statistics belong only one type of both tables or indexes. So, we added substructures
sych table and index inside ExtVacReport structure.
Therefore, we gather only for tables such statistics like number of scanned, removed pages,
their charecteristics according VM (all-visible and frozen). In addition, for tables we
gather number frozen, deleted and recently dead tuples and how many times vacuum processed
indexes for tables.
Controversally for indexes we gather number of deleted pages and deleted tuples only.
As for tables, deleted pages and deleted tuples reflect the overall performance of the vacuum
for the index relationship.
Since the vacuum cleans up references to tuple indexes before cleaning up table tuples,
which adds some complexity to the vacuum process, namely the vacuum switches from cleaning up
a table to its indexes and back during its operation, we need to save the vacuum statistics
collected for the heap before it starts cleaning up the indexes.
That's why it's necessary to track the vacuum statistics for the heap several times during
the vacuum procedure. To avoid sending the statistics to the Cumulative Statistics System
several times, we save these statistics in the LVRelState structure and only after vacuum
finishes cleaning up the heap, it sends them to the Cumulative Statistics System.
Authors: Alena Rybakina <[email protected]>,
Andrei Lepikhov <[email protected]>,
Andrei Zubkov <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>, Masahiko Sawada <[email protected]>,
Ilia Evdokimov <[email protected]>, jian he <[email protected]>,
Kirill Reshke <[email protected]>, Alexander Korotkov <[email protected]>,
Jim Nasby <[email protected]>, Sami Imseih <[email protected]>,
Karina Litskevich <[email protected]>
---
src/backend/access/heap/vacuumlazy.c | 232 +++++++++++++----
src/backend/catalog/system_views.sql | 32 +++
src/backend/commands/vacuumparallel.c | 10 +
src/backend/utils/activity/pgstat_relation.c | 45 ++--
src/backend/utils/adt/pgstatfuncs.c | 92 ++++++-
src/include/catalog/pg_proc.dat | 9 +
src/include/commands/vacuum.h | 25 ++
src/include/pgstat.h | 77 ++++--
.../vacuum-extending-in-repetable-read.out | 4 +-
.../t/050_vacuum_extending_basic_test.pl | 237 +++++++++++++++++-
src/test/regress/expected/rules.out | 22 ++
11 files changed, 681 insertions(+), 104 deletions(-)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 66e09d0a0cf..719ce90d96d 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -290,6 +290,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 */
@@ -412,6 +413,7 @@ typedef struct LVRelState
int32 wraparound_failsafe_count; /* number of emergency vacuums to
* prevent anti-wraparound
* shutdown */
+ ExtVacReport extVacReportIdx;
} LVRelState;
@@ -423,19 +425,6 @@ typedef struct LVSavedErrInfo
VacErrPhase phase;
} LVSavedErrInfo;
-/*
- * Counters and usage data for extended stats tracking.
- */
-typedef struct LVExtStatCounters
-{
- TimestampTz starttime;
- 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);
static void heap_vacuum_eager_scan_setup(LVRelState *vacrel,
@@ -565,27 +554,25 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
endtime = GetCurrentTimestamp();
TimestampDifference(counters->starttime, 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->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->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.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;
+ 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;
- report->total_time = secs * 1000. + usecs / 1000.;
+ report->total_time += secs * 1000. + usecs / 1000.;
if (!rel->pgstat_info || !pgstat_track_counts)
@@ -595,12 +582,122 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
*/
return;
- report->blks_fetched =
+ report->blks_fetched +=
rel->pgstat_info->counts.blocks_fetched - counters->blocks_fetched;
- report->blks_hit =
+ report->blks_hit +=
rel->pgstat_info->counts.blocks_hit - counters->blocks_hit;
}
+void
+extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters)
+{
+ /* Set initial values for common heap and index statistics */
+ 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;
+ }
+}
+
+void
+extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters, ExtVacReport * report)
+{
+ memset(report, 0, sizeof(ExtVacReport));
+
+ 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->tuples_deleted =
+ stats->tuples_removed - counters->tuples_removed;
+ report->index.pages_deleted =
+ stats->pages_deleted - counters->pages_deleted;
+ }
+}
+
+/* Accumulate vacuum statistics for heap.
+ *
+ * Because of complexity of vacuum processing: it switch procesing between
+ * the heap relation to index relations and visa versa, we need to store
+ * gathered statistics information for heap relations several times before
+ * the vacuum starts processing the indexes again.
+ *
+ * It is necessary to gather correct statistics information for heap and indexes
+ * otherwice the index statistics information would be added to his parent heap
+ * statistics information and it would be difficult to analyze it later.
+ *
+ * We can't subtract union vacuum statistics information for index from the heap relations
+ * because of total and delay time time statistics collecting during parallel vacuum
+ * procudure.
+*/
+static void
+accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats)
+{
+ /* Fill heap-specific extended stats fields */
+ extVacStats->type = PGSTAT_EXTVAC_TABLE;
+ extVacStats->table.pages_scanned = vacrel->scanned_pages;
+ extVacStats->table.pages_removed = vacrel->removed_pages;
+ extVacStats->table.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
+ extVacStats->table.vm_new_visible_pages = vacrel->vm_new_visible_pages;
+ extVacStats->table.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
+ extVacStats->tuples_deleted = vacrel->tuples_deleted;
+ extVacStats->table.tuples_frozen = vacrel->tuples_frozen;
+ extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
+ extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
+ extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
+ extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
+ extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
+ extVacStats->table.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+
+ extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
+ extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
+ extVacStats->total_blks_dirtied -= vacrel->extVacReportIdx.total_blks_dirtied;
+ extVacStats->total_blks_hit -= vacrel->extVacReportIdx.total_blks_hit;
+ extVacStats->total_blks_read -= vacrel->extVacReportIdx.total_blks_read;
+ extVacStats->total_blks_written -= vacrel->extVacReportIdx.total_blks_written;
+ extVacStats->wal_bytes -= vacrel->extVacReportIdx.wal_bytes;
+ extVacStats->wal_fpi -= vacrel->extVacReportIdx.wal_fpi;
+ extVacStats->wal_records -= vacrel->extVacReportIdx.wal_records;
+
+ extVacStats->total_time -= vacrel->extVacReportIdx.total_time;
+ extVacStats->delay_time -= vacrel->extVacReportIdx.delay_time;
+
+}
+
+static void
+accumulate_idxs_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacIdxStats)
+{
+ /* Fill heap-specific extended stats fields */
+ vacrel->extVacReportIdx.blk_read_time += extVacIdxStats->blk_read_time;
+ vacrel->extVacReportIdx.blk_write_time += extVacIdxStats->blk_write_time;
+ vacrel->extVacReportIdx.total_blks_dirtied += extVacIdxStats->total_blks_dirtied;
+ vacrel->extVacReportIdx.total_blks_hit += extVacIdxStats->total_blks_hit;
+ vacrel->extVacReportIdx.total_blks_read += extVacIdxStats->total_blks_read;
+ vacrel->extVacReportIdx.total_blks_written += extVacIdxStats->total_blks_written;
+ vacrel->extVacReportIdx.wal_bytes += extVacIdxStats->wal_bytes;
+ vacrel->extVacReportIdx.wal_fpi += extVacIdxStats->wal_fpi;
+ vacrel->extVacReportIdx.wal_records += extVacIdxStats->wal_records;
+ vacrel->extVacReportIdx.delay_time += extVacIdxStats->delay_time;
+
+ vacrel->extVacReportIdx.total_time += extVacIdxStats->total_time;
+}
+
/*
* Helper to set up the eager scanning state for vacuuming a single relation.
@@ -760,11 +857,9 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
char **indnames = NULL;
LVExtStatCounters extVacCounters;
ExtVacReport extVacReport;
- ExtVacReport allzero;
/* Initialize vacuum statistics */
- memset(&allzero, 0, sizeof(ExtVacReport));
- extVacReport = allzero;
+ memset(&extVacReport, 0, sizeof(ExtVacReport));
verbose = (params.options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -820,6 +915,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
+ memset(&vacrel->extVacReportIdx, 0, sizeof(ExtVacReport));
+
/* Set up high level stuff about rel and its indexes */
vacrel->rel = rel;
vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
@@ -1078,20 +1175,6 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
/* 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.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
- extVacReport.vm_new_visible_pages = vacrel->vm_new_visible_pages;
- extVacReport.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
- extVacReport.tuples_deleted = vacrel->tuples_deleted;
- extVacReport.tuples_frozen = vacrel->tuples_frozen;
- extVacReport.recently_dead_tuples = vacrel->recently_dead_tuples;
- extVacReport.missed_dead_tuples = vacrel->missed_dead_tuples;
- extVacReport.missed_dead_pages = vacrel->missed_dead_pages;
- extVacReport.index_vacuum_count = vacrel->num_index_scans;
- extVacReport.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
-
/*
* Report results to the cumulative stats system, too.
*
@@ -1102,6 +1185,13 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
* soon in cases where the failsafe prevented significant amounts of heap
* vacuuming.
*/
+
+ /*
+ * Make generic extended vacuum stats report and fill heap-specific
+ * extended stats fields.
+ */
+ extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport);
+ accumulate_heap_vacuum_statistics(vacrel, &extVacReport);
pgstat_report_vacuum(rel,
Max(vacrel->new_live_tuples, 0),
vacrel->recently_dead_tuples +
@@ -2811,10 +2901,20 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
}
else
{
+ LVExtStatCounters counters;
+ ExtVacReport extVacReport;
+
+ memset(&extVacReport, 0, sizeof(ExtVacReport));
+
+ extvac_stats_start(vacrel->rel, &counters);
+
/* Outsource everything to parallel variant */
parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples,
vacrel->num_index_scans);
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
+
/*
* Do a postcheck to consider applying wraparound failsafe now. Note
* that parallel VACUUM only gets the precheck and this postcheck.
@@ -3244,10 +3344,20 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
}
else
{
+ LVExtStatCounters counters;
+ ExtVacReport extVacReport;
+
+ memset(&extVacReport, 0, sizeof(ExtVacReport));
+
+ extvac_stats_start(vacrel->rel, &counters);
+
/* Outsource everything to parallel variant */
parallel_vacuum_cleanup_all_indexes(vacrel->pvs, reltuples,
vacrel->num_index_scans,
estimated_count);
+
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
}
/* Reset the progress counters */
@@ -3273,6 +3383,11 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
{
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
+ LVExtStatCountersIdx extVacCounters;
+ ExtVacReport extVacReport;
+
+ /* Set initial statistics values to gather vacuum statistics for the index */
+ extvac_stats_start_idx(indrel, istat, &extVacCounters);
ivinfo.index = indrel;
ivinfo.heaprel = vacrel->rel;
@@ -3291,6 +3406,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);
@@ -3299,6 +3415,15 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
istat = vac_bulkdel_one_index(&ivinfo, istat, vacrel->dead_items,
vacrel->dead_items_info);
+ /* Make extended vacuum stats report for index */
+ extvac_stats_end_idx(indrel, istat, &extVacCounters, &extVacReport);
+
+ if (!ParallelVacuumIsActive(vacrel))
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
+
+ pgstat_report_vacuum(indrel,
+ 0, 0, 0, &extVacReport);
+
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
pfree(vacrel->indname);
@@ -3323,6 +3448,11 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
{
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
+ LVExtStatCountersIdx extVacCounters;
+ ExtVacReport extVacReport;
+
+ /* Set initial statistics values to gather vacuum statistics for the index */
+ extvac_stats_start_idx(indrel, istat, &extVacCounters);
ivinfo.index = indrel;
ivinfo.heaprel = vacrel->rel;
@@ -3342,12 +3472,22 @@ 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);
+
+ if (!ParallelVacuumIsActive(vacrel))
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
+
+ pgstat_report_vacuum(indrel,
+ 0, 0, 0, &extVacReport);
+
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
pfree(vacrel->indname);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ffb407d414f..47b6a00d297 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1502,3 +1502,35 @@ 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';
+
+CREATE VIEW pg_stat_vacuum_indexes AS
+SELECT
+ rel.oid as relid,
+ ns.nspname AS schemaname,
+ rel.relname AS relname,
+
+ total_blks_read AS total_blks_read,
+ total_blks_hit AS total_blks_hit,
+ total_blks_dirtied AS total_blks_dirtied,
+ total_blks_written AS total_blks_written,
+
+ rel_blks_read AS rel_blks_read,
+ rel_blks_hit AS rel_blks_hit,
+
+ pages_deleted AS pages_deleted,
+ tuples_deleted AS tuples_deleted,
+
+ wal_records AS wal_records,
+ wal_fpi AS wal_fpi,
+ wal_bytes AS wal_bytes,
+
+ blk_read_time AS blk_read_time,
+ blk_write_time AS blk_write_time,
+
+ delay_time AS delay_time,
+ total_time AS total_time
+FROM
+ pg_class rel
+ JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
+ LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
+WHERE rel.relkind = 'i';
\ No newline at end of file
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 114cd7c31d3..43450685b09 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -868,6 +868,8 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
IndexBulkDeleteResult *istat = NULL;
IndexBulkDeleteResult *istat_res;
IndexVacuumInfo ivinfo;
+ LVExtStatCountersIdx extVacCounters;
+ ExtVacReport extVacReport;
/*
* Update the pointer to the corresponding bulk-deletion result if someone
@@ -876,6 +878,9 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
if (indstats->istat_updated)
istat = &(indstats->istat);
+ /* Set initial statistics values to gather vacuum statistics for the index */
+ extvac_stats_start_idx(indrel, &(indstats->istat), &extVacCounters);
+
ivinfo.index = indrel;
ivinfo.heaprel = pvs->heaprel;
ivinfo.analyze_only = false;
@@ -904,6 +909,11 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
RelationGetRelationName(indrel));
}
+ /* Make extended vacuum stats report for index */
+ extvac_stats_end_idx(indrel, istat_res, &extVacCounters, &extVacReport);
+ pgstat_report_vacuum(indrel,
+ 0, 0, 0, &extVacReport);
+
/*
* Copy the index bulk-deletion result returned from ambulkdelete and
* amvacuumcleanup to the DSM segment if it's the first cycle because they
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 361713479e8..4bd6afc3794 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -1036,20 +1036,35 @@ 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->vm_new_frozen_pages += src->vm_new_frozen_pages;
- dst->vm_new_visible_pages += src->vm_new_visible_pages;
- dst->vm_new_visible_frozen_pages += src->vm_new_visible_frozen_pages;
- dst->tuples_deleted += src->tuples_deleted;
- dst->tuples_frozen += src->tuples_frozen;
- dst->recently_dead_tuples += src->recently_dead_tuples;
- dst->index_vacuum_count += src->index_vacuum_count;
- dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
- dst->missed_dead_pages += src->missed_dead_pages;
- dst->missed_dead_tuples += src->missed_dead_tuples;
+ if (dst->type == PGSTAT_EXTVAC_INVALID)
+ dst->type = src->type;
+ Assert(src->type == PGSTAT_EXTVAC_INVALID || src->type == dst->type);
+
+ if (dst->type == src->type)
+ {
+ dst->blks_fetched += src->blks_fetched;
+ dst->blks_hit += src->blks_hit;
+
+ if (dst->type == PGSTAT_EXTVAC_TABLE)
+ {
+ dst->table.pages_scanned += src->table.pages_scanned;
+ dst->table.pages_removed += src->table.pages_removed;
+ dst->table.vm_new_frozen_pages += src->table.vm_new_frozen_pages;
+ dst->table.vm_new_visible_pages += src->table.vm_new_visible_pages;
+ dst->table.vm_new_visible_frozen_pages += src->table.vm_new_visible_frozen_pages;
+ dst->tuples_deleted += src->tuples_deleted;
+ dst->table.tuples_frozen += src->table.tuples_frozen;
+ dst->table.recently_dead_tuples += src->table.recently_dead_tuples;
+ dst->table.index_vacuum_count += src->table.index_vacuum_count;
+ dst->table.missed_dead_pages += src->table.missed_dead_pages;
+ dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
+ dst->table.wraparound_failsafe_count += src->table.wraparound_failsafe_count;
+ }
+ else if (dst->type == PGSTAT_EXTVAC_INDEX)
+ {
+ dst->index.pages_deleted += src->index.pages_deleted;
+ dst->tuples_deleted += src->tuples_deleted;
+ }
+ }
}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index d7dfda0c1a7..755751c3b46 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2360,18 +2360,19 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
extvacuum->blks_hit);
values[i++] = Int64GetDatum(extvacuum->blks_hit);
- values[i++] = Int64GetDatum(extvacuum->pages_scanned);
- values[i++] = Int64GetDatum(extvacuum->pages_removed);
- values[i++] = Int64GetDatum(extvacuum->vm_new_frozen_pages);
- values[i++] = Int64GetDatum(extvacuum->vm_new_visible_pages);
- values[i++] = Int64GetDatum(extvacuum->vm_new_visible_frozen_pages);
- values[i++] = Int64GetDatum(extvacuum->missed_dead_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.pages_scanned);
+ values[i++] = Int64GetDatum(extvacuum->table.pages_removed);
+ values[i++] = Int64GetDatum(extvacuum->table.vm_new_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.missed_dead_pages);
values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
- values[i++] = Int64GetDatum(extvacuum->tuples_frozen);
- values[i++] = Int64GetDatum(extvacuum->recently_dead_tuples);
- values[i++] = Int64GetDatum(extvacuum->missed_dead_tuples);
- values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
- values[i++] = Int64GetDatum(extvacuum->index_vacuum_count);
+ values[i++] = Int64GetDatum(extvacuum->table.tuples_frozen);
+ values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
+ values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
+
+ values[i++] = Int32GetDatum(extvacuum->table.wraparound_failsafe_count);
+ values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
values[i++] = Int64GetDatum(extvacuum->wal_records);
values[i++] = Int64GetDatum(extvacuum->wal_fpi);
@@ -2393,3 +2394,72 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+/*
+ * Get the vacuum statistics for the heap tables.
+ */
+Datum
+pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 16
+
+ Oid relid = PG_GETARG_OID(0);
+ PgStat_StatTabEntry *tabentry;
+ ExtVacReport *extvacuum;
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
+ char buf[256];
+ int i = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tabentry = pgstat_fetch_stat_tabentry(relid);
+
+ if (tabentry == NULL)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
+ else
+ {
+ extvacuum = &(tabentry->vacuum_ext);
+ }
+
+ i = 0;
+
+ values[i++] = ObjectIdGetDatum(relid);
+
+ values[i++] = Int64GetDatum(extvacuum->total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+
+ values[i++] = Int64GetDatum(extvacuum->blks_fetched -
+ extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->blks_hit);
+
+ values[i++] = Int64GetDatum(extvacuum->index.pages_deleted);
+ values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+
+ values[i++] = Int64GetDatum(extvacuum->wal_records);
+ values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+ /* Convert to numeric, like pg_stat_statements */
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ values[i++] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ values[i++] = Float8GetDatum(extvacuum->blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->delay_time);
+ values[i++] = Float8GetDatum(extvacuum->total_time);
+
+ Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS);
+
+ /* Returns the record as Datum */
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 915a5a7822f..e957781b623 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12630,4 +12630,13 @@
proname => 'pg_stat_get_rev_all_frozen_pages', provolatile => 's',
proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
prosrc => 'pg_stat_get_rev_all_frozen_pages' },
+{ oid => '8004',
+ descr => 'pg_stat_get_vacuum_indexes return stats values',
+ proname => 'pg_stat_get_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
+ proretset => 't',
+ proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_deleted,tuples_deleted,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time}',
+ prosrc => 'pg_stat_get_vacuum_indexes' }
]
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 6b997bc7fb1..b48ace6084b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -25,6 +25,7 @@
#include "storage/buf.h"
#include "storage/lock.h"
#include "utils/relcache.h"
+#include "pgstat.h"
/*
* Flags for amparallelvacuumoptions to control the participation of bulkdelete
@@ -300,6 +301,26 @@ typedef struct VacDeadItemsInfo
int64 num_items; /* current # of entries */
} VacDeadItemsInfo;
+/*
+ * Counters and usage data for extended stats tracking.
+ */
+typedef struct LVExtStatCounters
+{
+ TimestampTz starttime;
+ WalUsage walusage;
+ BufferUsage bufusage;
+ double VacuumDelayTime;
+ PgStat_Counter blocks_fetched;
+ PgStat_Counter blocks_hit;
+} LVExtStatCounters;
+
+typedef struct LVExtStatCountersIdx
+{
+ LVExtStatCounters common;
+ int64 pages_deleted;
+ int64 tuples_removed;
+} LVExtStatCountersIdx;
+
/* GUC parameters */
extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for PostGIS */
extern PGDLLIMPORT int vacuum_freeze_min_age;
@@ -413,4 +434,8 @@ extern double anl_random_fract(void);
extern double anl_init_selection_state(int n);
extern double anl_get_next_S(double t, int n, double *stateptr);
+extern void extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters);
+extern void extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters, ExtVacReport * report);
#endif /* VACUUM_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 46d12fa3bd0..f2881dbb6f9 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -114,11 +114,19 @@ typedef struct PgStat_BackendSubEntry
PgStat_Counter conflict_count[CONFLICT_NUM_TYPES];
} PgStat_BackendSubEntry;
+/* Type of ExtVacReport */
+typedef enum ExtVacReportType
+{
+ PGSTAT_EXTVAC_INVALID = 0,
+ PGSTAT_EXTVAC_TABLE = 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
@@ -155,23 +163,58 @@ typedef struct ExtVacReport
* point, in msec */
double total_time; /* total time of a vacuum operation, in msec */
- int64 pages_scanned; /* heap pages examined (not skipped by VM) */
- int64 pages_removed; /* heap pages removed by vacuum "truncation" */
- int64 vm_new_frozen_pages; /* pages marked in VM as frozen */
- int64 vm_new_visible_pages; /* pages marked in VM as all-visible */
- int64 vm_new_visible_frozen_pages; /* pages marked in VM as
- * all-visible and frozen */
- int64 missed_dead_tuples; /* tuples not pruned by vacuum due to
- * failure to get a cleanup lock */
- int64 missed_dead_pages; /* pages with missed dead tuples */
int64 tuples_deleted; /* tuples deleted by vacuum */
- int64 tuples_frozen; /* tuples frozen up by vacuum */
- int64 recently_dead_tuples; /* deleted tuples that are still
- * visible to some transaction */
- int64 index_vacuum_count; /* the number of index vacuumings */
- int32 wraparound_failsafe_count; /* number of emergency vacuums to
- * prevent anti-wraparound
- * shutdown */
+
+ 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; /* heap pages examined (not skipped by
+ * VM) */
+ int64 pages_removed; /* heap pages removed by vacuum
+ * "truncation" */
+ int64 pages_frozen; /* pages marked in VM as frozen */
+ int64 pages_all_visible; /* pages marked in VM as
+ * all-visible */
+ int64 tuples_frozen; /* tuples frozen up by vacuum */
+ int64 recently_dead_tuples; /* deleted tuples that are
+ * still visible to some
+ * transaction */
+ int64 vm_new_frozen_pages; /* pages marked in VM as
+ * frozen */
+ int64 vm_new_visible_pages; /* pages marked in VM as
+ * all-visible */
+ int64 vm_new_visible_frozen_pages; /* pages marked in VM as
+ * all-visible and
+ * frozen */
+ int64 missed_dead_tuples; /* tuples not pruned by vacuum due
+ * to failure to get a cleanup
+ * lock */
+ int64 missed_dead_pages; /* pages with missed dead tuples */
+ int64 index_vacuum_count; /* number of index vacuumings */
+ int32 wraparound_failsafe_count; /* number of emergency
+ * vacuums to prevent
+ * anti-wraparound
+ * shutdown */
+ } table;
+ struct
+ {
+ int64 pages_deleted; /* number of pages deleted by vacuum */
+ } index;
+ } /* per_type_stats */ ;
} ExtVacReport;
/* ----------
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 87f7e40b4a6..6d960423912 100644
--- a/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
+++ b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
@@ -34,7 +34,7 @@ step s2_print_vacuum_stats_table:
relname |tuples_deleted|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
--------------------------+--------------+--------------------+------------------+-----------------+-------------
-test_vacuum_stat_isolation| 0| 100| 0| 0| 0
+test_vacuum_stat_isolation| 0| 600| 0| 0| 0
(1 row)
step s1_commit: COMMIT;
@@ -48,6 +48,6 @@ step s2_print_vacuum_stats_table:
relname |tuples_deleted|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
--------------------------+--------------+--------------------+------------------+-----------------+-------------
-test_vacuum_stat_isolation| 100| 100| 0| 0| 101
+test_vacuum_stat_isolation| 300| 600| 0| 0| 303
(1 row)
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index 7e25a3fe63f..8f7b1e2909b 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -2,9 +2,10 @@
# Test cumulative vacuum stats system using TAP
#
# This test validates the accuracy and behavior of cumulative vacuum statistics
-# across tables using:
+# across heap tables, indexes using:
#
# • pg_stat_vacuum_tables
+# • pg_stat_vacuum_indexes
#
# A polling helper function repeatedly checks the stats views until expected
# deltas appear or a configurable timeout expires. This guarantees that
@@ -62,7 +63,7 @@ $node->safe_psql($dbname, q{
$node->safe_psql(
$dbname,
- "CREATE TABLE vestat (x int)
+ "CREATE TABLE vestat (x int PRIMARY KEY)
WITH (autovacuum_enabled = off, fillfactor = 10);
INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
ANALYZE vestat;"
@@ -80,12 +81,15 @@ my $updated = 0;
#------------------------------------------------------------------------------
# wait_for_vacuum_stats
#
-# Polls pg_stat_vacuum_tables until the table-level counters exceed
-# the provided baselines, or until the configured timeout elapses.
+# Polls pg_stat_vacuum_tables and pg_stat_vacuum_indexes until both the
+# table-level and index-level counters exceed the provided baselines, or until
+# the configured timeout elapses.
#
# Expected named args (baseline values):
# tab_tuples_deleted
# tab_wal_records
+# idx_tuples_deleted
+# idx_wal_records
#
# Returns: 1 if the condition is met before timeout, 0 otherwise.
#------------------------------------------------------------------------------
@@ -94,6 +98,8 @@ sub wait_for_vacuum_stats {
my (%args) = @_;
my $tab_tuples_deleted = $args{tab_tuples_deleted} or 0;
my $tab_wal_records = $args{tab_wal_records} or 0;
+ my $idx_tuples_deleted = $args{idx_tuples_deleted} or 0;
+ my $idx_wal_records = $args{idx_wal_records} or 0;
my $start = time();
while ((time() - $start) < $timeout) {
@@ -101,9 +107,14 @@ sub wait_for_vacuum_stats {
my $result_query = $node->safe_psql(
$dbname,
"VACUUM vestat;
- SELECT tuples_deleted > $tab_tuples_deleted AND wal_records > $tab_wal_records
+ SELECT
+ (SELECT (tuples_deleted > $tab_tuples_deleted AND wal_records > $tab_wal_records)
FROM pg_stat_vacuum_tables
- WHERE relname = 'vestat';"
+ WHERE relname = 'vestat')
+ AND
+ (SELECT (tuples_deleted > $idx_tuples_deleted AND wal_records > $idx_wal_records)
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey');"
);
return 1 if ($result_query eq 't');
@@ -126,6 +137,12 @@ my $wal_records = 0;
my $wal_bytes = 0;
my $wal_fpi = 0;
+my $index_tuples_deleted = 0;
+my $index_pages_deleted = 0;
+my $index_wal_records = 0;
+my $index_wal_bytes = 0;
+my $index_wal_fpi = 0;
+
my $pages_frozen_prev = 0;
my $tuples_deleted_prev = 0;
my $pages_scanned_prev = 0;
@@ -134,11 +151,17 @@ my $wal_records_prev = 0;
my $wal_bytes_prev = 0;
my $wal_fpi_prev = 0;
+my $index_tuples_deleted_prev = 0;
+my $index_pages_deleted_prev = 0;
+my $index_wal_records_prev = 0;
+my $index_wal_bytes_prev = 0;
+my $index_wal_fpi_prev = 0;
+
#------------------------------------------------------------------------------
# fetch_vacuum_stats
#
-# Reads current values of relevant vacuum counters for the test table,
-# storing them in package variables for subsequent comparisons.
+# Reads current values of relevant vacuum counters for the test table and its
+# primary index, storing them in package variables for subsequent comparisons.
#------------------------------------------------------------------------------
sub fetch_vacuum_stats {
@@ -153,6 +176,18 @@ sub fetch_vacuum_stats {
$base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
($pages_frozen, $tuples_deleted, $pages_scanned, $pages_removed, $wal_records, $wal_bytes, $wal_fpi)
= split /\s+/, $base_statistics;
+
+ # --- index stats ---
+ my $index_base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT tuples_deleted, pages_deleted, wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+ );
+
+ $index_base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
+ ($index_tuples_deleted, $index_pages_deleted, $index_wal_records, $index_wal_bytes, $index_wal_fpi)
+ = split /\s+/, $index_base_statistics;
}
#------------------------------------------------------------------------------
@@ -169,6 +204,12 @@ sub save_vacuum_stats {
$wal_records_prev = $wal_records;
$wal_bytes_prev = $wal_bytes;
$wal_fpi_prev = $wal_fpi;
+
+ $index_tuples_deleted_prev = $index_tuples_deleted;
+ $index_pages_deleted_prev = $index_pages_deleted;
+ $index_wal_records_prev = $index_wal_records;
+ $index_wal_bytes_prev = $index_wal_bytes;
+ $index_wal_fpi_prev = $index_wal_fpi;
}
#------------------------------------------------------------------------------
@@ -195,7 +236,20 @@ sub print_vacuum_stats_on_error {
" pages_removed = $pages_removed\n" .
" wal_records = $wal_records\n" .
" wal_bytes = $wal_bytes\n" .
- " wal_fpi = $wal_fpi\n"
+ " wal_fpi = $wal_fpi\n" .
+ "Index statistics:\n" .
+ " Before test:\n" .
+ " tuples_deleted = $index_tuples_deleted_prev\n" .
+ " pages_removed = $index_pages_deleted_prev\n" .
+ " wal_records = $index_wal_records_prev\n" .
+ " wal_bytes = $index_wal_bytes_prev\n" .
+ " wal_fpi = $index_wal_fpi_prev\n" .
+ " After test:\n" .
+ " tuples_deleted = $index_tuples_deleted\n" .
+ " pages_removed = $index_pages_deleted\n" .
+ " wal_records = $index_wal_records\n" .
+ " wal_bytes = $index_wal_bytes\n" .
+ " wal_fpi = $index_wal_fpi\n"
);
};
@@ -203,7 +257,8 @@ sub print_vacuum_stats_on_error {
# fetch_vacuum_stats during mismatch
#
# Print current values and old values of relevant vacuum counters for the test
-# table, storing them in package variables for subsequent comparisons.
+# table and its primary index, storing them in package variables for subsequent
+# comparisons.
#------------------------------------------------------------------------------
sub fetch_error_base_tab_vacuum_statistics {
@@ -258,6 +313,54 @@ sub fetch_error_wal_tab_vacuum_statistics {
);
}
+sub fetch_error_base_idx_vacuum_statistics {
+
+ # fetch actual base vacuum statistics
+ my $base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT tuples_deleted, pages_deleted
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+ );
+ $base_statistics =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_tuples_deleted, $cur_pages_deleted) = split /\s+/, $base_statistics;
+
+ diag(
+ "BASE STATS MISMATCH FOR INDEX:\n" .
+ " Baseline:\n" .
+ " tuples_deleted = $index_tuples_deleted\n" .
+ " pages_removed = $index_pages_deleted\n" .
+ " Current:\n" .
+ " tuples_deleted = $cur_tuples_deleted\n" .
+ " pages_deleted = $cur_pages_deleted\n"
+ );
+}
+
+sub fetch_error_wal_idx_vacuum_statistics {
+
+ my $wal_raw = $node->safe_psql(
+ $dbname,
+ "SELECT wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+ );
+
+ $wal_raw =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_wal_rec, $cur_wal_bytes, $cur_wal_fpi) = split /\s+/, $wal_raw;
+
+ diag(
+ "WAL STATS MISMATCH FOR INDEX:\n" .
+ " Baseline:\n" .
+ " wal_records = $index_wal_records\n" .
+ " wal_bytes = $index_wal_bytes\n" .
+ " wal_fpi = $index_wal_fpi\n" .
+ " Current:\n" .
+ " wal_records = $cur_wal_rec\n" .
+ " wal_bytes = $cur_wal_bytes\n" .
+ " wal_fpi = $cur_wal_fpi\n"
+ );
+}
+
#------------------------------------------------------------------------------
# Test 1: Delete half the rows, run VACUUM, and wait for stats to advance
#------------------------------------------------------------------------------
@@ -270,7 +373,9 @@ $node->safe_psql($dbname, "VACUUM vestat;");
# Poll the stats view until expected deltas appear or timeout
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => 0,
- tab_wal_records => 0
+ tab_wal_records => 0,
+ idx_tuples_deleted => 0,
+ idx_wal_records => 0,
);
ok($updated, 'vacuum stats updated after vacuuming half-deleted table (tuples_deleted and wal_fpi advanced)')
or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds after vacuuming half-deleted table";
@@ -290,6 +395,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted > $index_tuples_deleted_prev, 'index tuples_deleted has increased');
+ok($index_wal_records > $index_wal_records_prev, 'index wal_records has increased');
+ok($index_wal_bytes > $index_wal_bytes_prev, 'index wal_bytes has increased');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -307,6 +418,8 @@ $node->safe_psql($dbname, "VACUUM vestat;");
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => $tuples_deleted_prev,
tab_wal_records => $wal_records_prev,
+ idx_tuples_deleted => $index_tuples_deleted_prev,
+ idx_wal_records => $index_wal_records_prev,
);
ok($updated, 'vacuum stats updated after vacuuming all-deleted table (tuples_deleted and wal_records advanced)')
@@ -327,6 +440,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted > $index_pages_deleted_prev, 'index pages_deleted has increased');
+ok($index_tuples_deleted > $index_tuples_deleted_prev, 'index tuples_deleted has increased');
+ok($index_wal_records > $index_wal_records_prev, 'index wal_records has increased');
+ok($index_wal_bytes > $index_wal_bytes_prev, 'index wal_bytes has increased');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -357,6 +476,12 @@ ok($wal_records == $wal_records_prev, 'table wal_records stay the same');
ok($wal_bytes == $wal_bytes_prev, 'table wal_bytes stay the same');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted == $index_tuples_deleted_prev, 'index tuples_deleted stay the same');
+ok($index_wal_records == $index_wal_records_prev, 'index wal_records stay the same');
+ok($index_wal_bytes == $index_wal_bytes_prev, 'index wal_bytes stay the same');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -379,6 +504,8 @@ $node->safe_psql(
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => $tuples_deleted,
tab_wal_records => $wal_records,
+ idx_tuples_deleted => $index_tuples_deleted,
+ idx_wal_records => $index_wal_records,
);
ok($updated, 'vacuum stats updated after updating tuples in the table (tuples_deleted and wal_records advanced)')
@@ -399,6 +526,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+ok($index_pages_deleted > $index_pages_deleted_prev, 'index pages_deleted has increased');
+ok($index_tuples_deleted > $index_tuples_deleted_prev, 'index tuples_deleted has increased');
+ok($index_wal_records > $index_wal_records_prev, 'index wal_records has increased');
+ok($index_wal_bytes > $index_wal_bytes_prev, 'index wal_bytes has increased');
+ok($index_wal_fpi > $index_wal_fpi_prev, 'index wal_fpi has increased');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -421,7 +554,9 @@ $node->safe_psql($dbname, "VACUUM vestat;");
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => 0,
- tab_wal_records => $wal_records_prev
+ tab_wal_records => $wal_records_prev,
+ idx_tuples_deleted => 0,
+ idx_wal_records => 0,
);
ok($updated, 'vacuum stats updated after updating tuples and trancation in the table (tuples_deleted and wal_records advanced)')
@@ -442,6 +577,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted == $index_tuples_deleted_prev, 'index tuples_deleted stay the same');
+ok($index_wal_records == $index_wal_records_prev, 'index wal_records stay the same');
+ok($index_wal_bytes == $index_wal_bytes_prev, 'index wal_bytes stay the same');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -464,7 +605,9 @@ $node->safe_psql(
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => 0,
- tab_wal_records => $wal_records
+ tab_wal_records => $wal_records,
+ idx_tuples_deleted => 0,
+ idx_wal_records => 0,
);
ok($updated, 'vacuum stats updated after deleting all tuples and trancation in the table (tuples_deleted and wal_records advanced)')
@@ -485,6 +628,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted == $index_tuples_deleted_prev, 'index tuples_deleted stay the same');
+ok($index_wal_records == $index_wal_records_prev, 'index wal_records stay the same');
+ok($index_wal_bytes == $index_wal_bytes_prev, 'index wal_bytes stay the same');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -513,6 +662,34 @@ $base_stats = $node->safe_psql(
);
ok($base_stats eq 't', 'heap vacuum stats return from the current relation and database as expected');
+$reloid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'vestat_pkey';
+ }
+);
+
+# Check if we can get vacuum statistics of particular index relation in the current database
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 1 FROM pg_stat_vacuum_indexes($dboid, $reloid);"
+);
+ok($base_stats eq 't', 'index vacuum stats return from the current relation and database as expected');
+
+# Check if we return empty results if vacuum statistics with particular oid doesn't exist
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 0 FROM pg_stats_vacuum_tables($dboid, 1);"
+);
+ok($base_stats eq 't', 'table vacuum stats return no rows, as expected');
+
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 0 FROM pg_stat_vacuum_indexes($dboid, 1);"
+);
+ok($base_stats eq 't', 'index vacuum stats return no rows, as expected');
+
+
#------------------------------------------------------------------------------
# Test 9: Check relation-level vacuum statistics from another database
#------------------------------------------------------------------------------
@@ -523,6 +700,14 @@ $base_stats = $node->safe_psql(
FROM pg_stat_vacuum_tables
WHERE relname = 'vestat';"
);
+ok($base_stats eq 't', 'check the printing table vacuum extended statistics from another database are not available');
+
+$base_stats = $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) = 0
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+);
ok($base_stats eq 't', 'check the printing heap vacuum extended statistics from another database are not available');
$reloid = $node->safe_psql(
@@ -543,6 +728,23 @@ $base_stats = $node->safe_psql(
is($base_stats, 't', 'vacuum stats for common heap objects available');
+my $indoid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'pg_shdepend_reference_index';
+ }
+);
+
+$base_stats = $node->safe_psql(
+ $dbname,
+ qq{
+ SELECT count(*) = 1
+ FROM pg_stat_vacuum_indexes(0, $indoid);
+ }
+);
+
+is($base_stats, 't', 'vacuum stats for common index objects available');
+
#------------------------------------------------------------------------------
# Test 11: Cleanup checks: ensure functions return empty sets for OID = 0
#------------------------------------------------------------------------------
@@ -562,6 +764,15 @@ $base_stats = $node->safe_psql(
);
ok($base_stats eq 't', 'pg_stat_vacuum_tables correctly returns no rows for OID = 0');
+$base_stats = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT COUNT(*) = 0
+ FROM pg_stat_vacuum_indexes WHERE relid = 0;
+ }
+);
+ok($base_stats eq 't', 'pg_stat_vacuum_indexes correctly returns no rows for OID = 0');
+
$node->safe_psql('postgres',
"DROP DATABASE $dbname;"
);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e4a77878beb..7e6029394cb 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,6 +2330,28 @@ pg_stat_user_tables| SELECT relid,
rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
+ ns.nspname AS schemaname,
+ rel.relname,
+ stats.total_blks_read,
+ stats.total_blks_hit,
+ stats.total_blks_dirtied,
+ stats.total_blks_written,
+ stats.rel_blks_read,
+ stats.rel_blks_hit,
+ stats.pages_deleted,
+ stats.tuples_deleted,
+ stats.wal_records,
+ stats.wal_fpi,
+ stats.wal_bytes,
+ stats.blk_read_time,
+ stats.blk_write_time,
+ stats.delay_time,
+ stats.total_time
+ FROM (pg_class rel
+ JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
+ LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (rel.relkind = 'i'::"char");
pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
rel.relname,
stats.relid,
--
2.39.5 (Apple Git-154)
From 2051db2600566dc88040cee97868469aa35440d7 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Mon, 1 Sep 2025 21:43:33 +0300
Subject: [PATCH 3/5] Machinery for grabbing an extended vacuum statistics on
databases.
Database vacuum statistics information is the collected general
vacuum statistics indexes and tables owned by the databases, which
they belong to.
In addition to the fact that there are far fewer databases in a system
than relations, vacuum statistics for a database contain fewer statistics
than relations, but they are enough to indicate that something may be
wrong in the system and prompt the administrator to enable extended
monitoring for relations.
So, buffer, wal, statistics of I/O time of read and writen blocks
statistics will be observed because they are collected for both
tables, indexes. In addition, we show the number of errors caught
during operation of the vacuum only for the error level.
wraparound_failsafe_count is a number of times when the vacuum starts
urgent cleanup to prevent wraparound problem which is critical for
the database.
Authors: Alena Rybakina <[email protected]>,
Andrei Lepikhov <[email protected]>,
Andrei Zubkov <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>, Masahiko Sawada <[email protected]>,
Ilia Evdokimov <[email protected]>, jian he <[email protected]>,
Kirill Reshke <[email protected]>, Alexander Korotkov <[email protected]>,
Jim Nasby <[email protected]>, Sami Imseih <[email protected]>
---
src/backend/access/heap/vacuumlazy.c | 2 +-
src/backend/catalog/system_views.sql | 26 +++++++-
src/backend/utils/activity/pgstat_database.c | 1 +
src/backend/utils/activity/pgstat_relation.c | 13 +++-
src/backend/utils/adt/pgstatfuncs.c | 62 ++++++++++++++++++-
src/include/catalog/pg_proc.dat | 13 +++-
src/include/pgstat.h | 7 +--
.../vacuum-extending-in-repetable-read.spec | 6 ++
.../t/050_vacuum_extending_basic_test.pl | 49 ++++++++++++---
.../t/051_vacuum_extending_freeze_test.pl | 48 +++++---------
src/test/regress/expected/rules.out | 17 +++++
11 files changed, 195 insertions(+), 49 deletions(-)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 719ce90d96d..fcd92a43dda 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -663,7 +663,7 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats
extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
- extVacStats->table.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+ extVacStats->wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 47b6a00d297..dc86b1ee212 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1533,4 +1533,28 @@ FROM
pg_class rel
JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
-WHERE rel.relkind = 'i';
\ No newline at end of file
+WHERE rel.relkind = 'i';
+
+CREATE VIEW pg_stat_vacuum_database AS
+SELECT
+ db.oid as dboid,
+ db.datname AS dbname,
+
+ stats.db_blks_read AS db_blks_read,
+ stats.db_blks_hit AS db_blks_hit,
+ stats.total_blks_dirtied AS total_blks_dirtied,
+ stats.total_blks_written AS total_blks_written,
+
+ stats.wal_records AS wal_records,
+ stats.wal_fpi AS wal_fpi,
+ stats.wal_bytes AS wal_bytes,
+
+ stats.blk_read_time AS blk_read_time,
+ stats.blk_write_time AS blk_write_time,
+
+ stats.delay_time AS delay_time,
+ stats.total_time AS total_time,
+ stats.wraparound_failsafe AS wraparound_failsafe
+FROM
+ pg_database db,
+ LATERAL pg_stat_get_vacuum_database(db.oid) stats;
\ No newline at end of file
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index b31f20d41bc..65207d30378 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -485,6 +485,7 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
pgstat_unlock_entry(entry_ref);
memset(pendingent, 0, sizeof(*pendingent));
+ memset(&(pendingent)->vacuum_ext, 0, sizeof(ExtVacReport));
return true;
}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 4bd6afc3794..2675c541369 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -215,6 +215,7 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
PgStat_EntryRef *entry_ref;
PgStatShared_Relation *shtabentry;
PgStat_StatTabEntry *tabentry;
+ PgStatShared_Database *dbentry;
Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
TimestampTz ts;
PgStat_Counter elapsedtime;
@@ -273,6 +274,16 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
*/
pgstat_flush_io(false);
(void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
+
+ if (dboid != InvalidOid)
+ {
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
+ dboid, InvalidOid, false);
+ dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
+
+ pgstat_accumulate_extvac_stats(&dbentry->stats.vacuum_ext, params, false);
+ pgstat_unlock_entry(entry_ref);
+ }
}
/*
@@ -1032,6 +1043,7 @@ pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
dst->blk_write_time += src->blk_write_time;
dst->delay_time += src->delay_time;
dst->total_time += src->total_time;
+ dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
if (!accumulate_reltype_specific_info)
return;
@@ -1059,7 +1071,6 @@ pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
dst->table.index_vacuum_count += src->table.index_vacuum_count;
dst->table.missed_dead_pages += src->table.missed_dead_pages;
dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
- dst->table.wraparound_failsafe_count += src->table.wraparound_failsafe_count;
}
else if (dst->type == PGSTAT_EXTVAC_INDEX)
{
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 755751c3b46..4e2714f2e6a 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2371,7 +2371,7 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
- values[i++] = Int32GetDatum(extvacuum->table.wraparound_failsafe_count);
+ values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
values[i++] = Int64GetDatum(extvacuum->wal_records);
@@ -2463,3 +2463,63 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 14
+
+ Oid dbid = PG_GETARG_OID(0);
+ PgStat_StatDBEntry *dbentry;
+ ExtVacReport *extvacuum;
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
+ char buf[256];
+ int i = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ dbentry = pgstat_fetch_stat_dbentry(dbid);
+
+ if (dbentry == NULL)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
+ else
+ {
+ extvacuum = &(dbentry->vacuum_ext);
+ }
+
+ i = 0;
+
+ values[i++] = ObjectIdGetDatum(dbid);
+
+ values[i++] = Int64GetDatum(extvacuum->total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+
+ values[i++] = Int64GetDatum(extvacuum->wal_records);
+ values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+ /* Convert to numeric, like pg_stat_statements */
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ values[i++] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ values[i++] = Float8GetDatum(extvacuum->blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->delay_time);
+ values[i++] = Float8GetDatum(extvacuum->total_time);
+ values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+
+ Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
+
+ /* Returns the record as Datum */
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e957781b623..c3a2adb96f1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12631,12 +12631,21 @@
proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
prosrc => 'pg_stat_get_rev_all_frozen_pages' },
{ oid => '8004',
- descr => 'pg_stat_get_vacuum_indexes return stats values',
+ descr => 'pg_stat_get_vacuum_indexes returns vacuum stats values for index',
proname => 'pg_stat_get_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
proretset => 't',
proargtypes => 'oid',
proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8}',
proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
proargnames => '{reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_deleted,tuples_deleted,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time}',
- prosrc => 'pg_stat_get_vacuum_indexes' }
+ prosrc => 'pg_stat_get_vacuum_indexes' },
+{ oid => '8005',
+ descr => 'pg_stat_get_vacuum_database returns vacuum stats values for database',
+ proname => 'pg_stat_get_vacuum_database', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
+ proretset => 't',
+ proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,int4}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{dbid,dboid,db_blks_read,db_blks_hit,total_blks_dirtied,total_blks_written,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time,wraparound_failsafe}',
+ prosrc => 'pg_stat_get_vacuum_database' },
]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f2881dbb6f9..f3bdc1c38df 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -165,6 +165,9 @@ typedef struct ExtVacReport
int64 tuples_deleted; /* tuples deleted by vacuum */
+ int32 wraparound_failsafe_count; /* the number of times to prevent
+ * wraparound problem */
+
ExtVacReportType type; /* heap, index, etc. */
/* ----------
@@ -205,10 +208,6 @@ typedef struct ExtVacReport
* lock */
int64 missed_dead_pages; /* pages with missed dead tuples */
int64 index_vacuum_count; /* number of index vacuumings */
- int32 wraparound_failsafe_count; /* number of emergency
- * vacuums to prevent
- * anti-wraparound
- * shutdown */
} table;
struct
{
diff --git a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
index 5893d89573d..cfec3159580 100644
--- a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -18,6 +18,9 @@ teardown
}
session s1
+setup {
+ SET track_vacuum_statistics TO 'on';
+ }
step s1_begin_repeatable_read {
BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
select count(ival) from test_vacuum_stat_isolation where id>900;
@@ -25,6 +28,9 @@ step s1_begin_repeatable_read {
step s1_commit { COMMIT; }
session s2
+setup {
+ SET track_vacuum_statistics TO 'on';
+ }
step s2_insert { INSERT INTO test_vacuum_stat_isolation(id, ival) SELECT ival, ival%10 FROM generate_series(1,1000) As ival; }
step s2_update { UPDATE test_vacuum_stat_isolation SET ival = ival + 2 where id > 900; }
step s2_delete { DELETE FROM test_vacuum_stat_isolation where id > 900; }
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index 8f7b1e2909b..bd3cb544e30 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -2,10 +2,11 @@
# Test cumulative vacuum stats system using TAP
#
# This test validates the accuracy and behavior of cumulative vacuum statistics
-# across heap tables, indexes using:
+# across heap tables, indexes, and databases using:
#
# • pg_stat_vacuum_tables
# • pg_stat_vacuum_indexes
+# • pg_stat_vacuum_database
#
# A polling helper function repeatedly checks the stats views until expected
# deltas appear or a configurable timeout expires. This guarantees that
@@ -672,20 +673,20 @@ $reloid = $node->safe_psql(
# Check if we can get vacuum statistics of particular index relation in the current database
$base_stats = $node->safe_psql(
$dbname,
- "SELECT count(*) = 1 FROM pg_stat_vacuum_indexes($dboid, $reloid);"
+ "SELECT count(*) = 1 FROM pg_stat_get_vacuum_indexes($reloid);"
);
ok($base_stats eq 't', 'index vacuum stats return from the current relation and database as expected');
# Check if we return empty results if vacuum statistics with particular oid doesn't exist
$base_stats = $node->safe_psql(
$dbname,
- "SELECT count(*) = 0 FROM pg_stats_vacuum_tables($dboid, 1);"
+ "SELECT count(*) = 0 FROM pg_stat_get_vacuum_tables(1);"
);
ok($base_stats eq 't', 'table vacuum stats return no rows, as expected');
$base_stats = $node->safe_psql(
$dbname,
- "SELECT count(*) = 0 FROM pg_stat_vacuum_indexes($dboid, 1);"
+ "SELECT count(*) = 0 FROM pg_stat_get_vacuum_indexes(1);"
);
ok($base_stats eq 't', 'index vacuum stats return no rows, as expected');
@@ -708,7 +709,31 @@ $base_stats = $node->safe_psql(
FROM pg_stat_vacuum_indexes
WHERE relname = 'vestat_pkey';"
);
-ok($base_stats eq 't', 'check the printing heap vacuum extended statistics from another database are not available');
+ok($base_stats eq 't', 'check the printing index vacuum extended statistics from another database are not available');
+
+#--------------------------------------------------------------------------------------
+# Test 10: Check database-level vacuum statistics from the current and another database
+#--------------------------------------------------------------------------------------
+
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT db_blks_hit > 0 AND total_blks_dirtied > 0
+ AND total_blks_written > 0 AND wal_records > 0
+ AND wal_fpi > 0 AND wal_bytes > 0
+ FROM pg_stat_vacuum_database, pg_database
+ WHERE pg_database.datname = '$dbname'
+ AND pg_database.oid = pg_stat_vacuum_database.dboid;"
+);
+ok($base_stats eq 't', 'check database-level vacuum stats from the current database are available');
+
+$base_stats = $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) > 0
+ FROM pg_stat_vacuum_database, pg_database
+ WHERE pg_database.datname = '$dbname'
+ AND pg_database.oid = pg_stat_vacuum_database.dboid;"
+);
+ok($base_stats eq 't', 'check database-level vacuum stats from another database are available');
$reloid = $node->safe_psql(
$dbname,
@@ -739,7 +764,7 @@ $base_stats = $node->safe_psql(
$dbname,
qq{
SELECT count(*) = 1
- FROM pg_stat_vacuum_indexes(0, $indoid);
+ FROM pg_stat_get_vacuum_indexes($indoid);
}
);
@@ -773,8 +798,18 @@ $base_stats = $node->safe_psql(
);
ok($base_stats eq 't', 'pg_stat_vacuum_indexes correctly returns no rows for OID = 0');
+$base_stats = $node->safe_psql(
+ 'postgres',
+ q{
+ SELECT COUNT(*) = 0
+ FROM pg_stat_vacuum_database WHERE dboid = 0;
+ }
+);
+ok($base_stats eq 't', 'pg_stat_vacuum_database correctly returns no rows for OID = 0');
+
$node->safe_psql('postgres',
- "DROP DATABASE $dbname;"
+ "DROP DATABASE $dbname;
+ VACUUM;"
);
$node->stop;
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
index a9b5d6cb739..7528f20098b 100644
--- a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -91,11 +91,17 @@ sub wait_for_vacuum_stats {
my $start = time();
my $sql;
+ my $vacuum_run = 0;
+
+ # Run VACUUM once if requested, before polling
+ if ($run_vacuum) {
+ $node->safe_psql($dbname, 'VACUUM (FREEZE, VERBOSE) vestat');
+ $vacuum_run = 1;
+ }
while ((time() - $start) < $timeout) {
if ($run_vacuum) {
- $node->safe_psql($dbname, 'VACUUM (FREEZE, VERBOSE) vestat');
$sql = "
SELECT ($tab_frozen_column > $tab_all_frozen_pages_count AND
$tab_visible_column > $tab_all_visible_pages_count)
@@ -213,20 +219,6 @@ $node->safe_psql($dbname, q{
VACUUM (FREEZE, VERBOSE) vestat;
});
-# Poll the stats view until the expected deltas appear or timeout.
-# We do not expect rev_all_* counters to change here, so we pass -1 for them.
-$updated = wait_for_vacuum_stats(
- tab_frozen_column => 'vm_new_frozen_pages',
- tab_visible_column => 'vm_new_visible_pages',
- tab_all_frozen_pages_count => 0,
- tab_all_visible_pages_count => 0,
- run_vacuum => 1,
-);
-
-ok($updated,
- 'vacuum stats updated after vacuuming the table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
- or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
-
#------------------------------------------------------------------------------
# Snapshot current statistics for later comparison
#------------------------------------------------------------------------------
@@ -238,7 +230,7 @@ fetch_vacuum_stats();
#------------------------------------------------------------------------------
$res = $node->safe_psql($dbname, q{
- SELECT vm_new_frozen_pages > 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+ SELECT vm_new_frozen_pages = 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
});
ok($res eq 't', 'vacuum froze some pages, as expected') or
fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
@@ -335,32 +327,24 @@ fetch_vacuum_stats();
$node->safe_psql($dbname, q{ VACUUM (FREEZE, VERBOSE) vestat; });
-# Poll until stats update or timeout.
-# We pass current snapshot values for vm_new_frozen_pages/vm_new_visible_pages and expect rev counters unchanged.
-$updated = wait_for_vacuum_stats(
- tab_frozen_column => 'vm_new_frozen_pages',
- tab_visible_column => 'vm_new_visible_pages',
- tab_all_frozen_pages_count => $vm_new_frozen_pages,
- tab_all_visible_pages_count => $vm_new_visible_pages,
- run_vacuum => 1,
-);
-
-ok($updated,
- 'vacuum stats updated after vacuuming the all-updated table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
- or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
-
#------------------------------------------------------------------------------
# Verify statistics after final vacuum
# Check updated stats after backend work
#------------------------------------------------------------------------------
+
+# Fetch updated statistics to get the new baseline for comparison
+my $old_vm_new_frozen_pages = $vm_new_frozen_pages;
+my $old_vm_new_visible_pages = $vm_new_visible_pages;
+fetch_vacuum_stats();
+
$res = $node->safe_psql($dbname,
- "SELECT vm_new_frozen_pages > $vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+ "SELECT vm_new_frozen_pages = $old_vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
);
ok($res eq 't', 'vacuum froze some pages after backend activity, as expected') or
fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
$res = $node->safe_psql($dbname,
- "SELECT vm_new_visible_pages > $vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+ "SELECT vm_new_visible_pages > $old_vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
);
ok($res eq 't', 'vacuum marked pages all-visible after backend activity, as expected') or
fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value => $vm_new_visible_pages,);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7e6029394cb..b627c85e332 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,6 +2330,23 @@ pg_stat_user_tables| SELECT relid,
rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_database| SELECT db.oid AS dboid,
+ db.datname AS dbname,
+ stats.db_blks_read,
+ stats.db_blks_hit,
+ stats.total_blks_dirtied,
+ stats.total_blks_written,
+ stats.wal_records,
+ stats.wal_fpi,
+ stats.wal_bytes,
+ stats.blk_read_time,
+ stats.blk_write_time,
+ stats.delay_time,
+ stats.total_time,
+ stats.wraparound_failsafe,
+ stats.errors
+ FROM pg_database db,
+ LATERAL pg_stat_get_vacuum_database(db.oid) stats(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
ns.nspname AS schemaname,
rel.relname,
--
2.39.5 (Apple Git-154)
From 12f4596c25e15d1f7566b87334615ad112cfb64e Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Sun, 21 Dec 2025 01:40:06 +0300
Subject: [PATCH 4/5] Vacuum statistics have been separated from regular
relation and database statistics to reduce memory usage. Dedicated
PGSTAT_KIND_VACUUM_RELATION and PGSTAT_KIND_VACUUM_DB entries were added to
the stats collector to efficiently allocate memory for vacuum-specific
metrics, which require significantly more space per relation.
---
src/backend/access/heap/vacuumlazy.c | 124 +++++-----
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 1 +
src/backend/catalog/system_views.sql | 177 ++++++++-------
src/backend/commands/dbcommands.c | 1 +
src/backend/commands/vacuumparallel.c | 5 +-
src/backend/utils/activity/Makefile | 1 +
src/backend/utils/activity/pgstat.c | 30 ++-
src/backend/utils/activity/pgstat_database.c | 10 +-
src/backend/utils/activity/pgstat_relation.c | 75 +-----
src/backend/utils/activity/pgstat_vacuum.c | 214 ++++++++++++++++++
src/backend/utils/adt/pgstatfuncs.c | 149 ++++++------
src/backend/utils/misc/guc_parameters.dat | 6 +
src/include/commands/vacuum.h | 2 +-
src/include/pgstat.h | 208 +++++++++--------
src/include/utils/pgstat_internal.h | 15 ++
src/include/utils/pgstat_kind.h | 4 +-
.../t/050_vacuum_extending_basic_test.pl | 21 +-
.../t/051_vacuum_extending_freeze_test.pl | 1 +
src/test/regress/expected/rules.out | 146 ++++++------
20 files changed, 735 insertions(+), 456 deletions(-)
create mode 100644 src/backend/utils/activity/pgstat_vacuum.c
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index fcd92a43dda..3f1ed040908 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -413,7 +413,8 @@ typedef struct LVRelState
int32 wraparound_failsafe_count; /* number of emergency vacuums to
* prevent anti-wraparound
* shutdown */
- ExtVacReport extVacReportIdx;
+
+ PgStat_VacuumRelationCounts extVacReportIdx;
} LVRelState;
@@ -505,6 +506,9 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
{
TimestampTz starttime;
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
memset(counters, 0, sizeof(LVExtStatCounters));
starttime = GetCurrentTimestamp();
@@ -536,7 +540,7 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
*/
static void
extvac_stats_end(Relation rel, LVExtStatCounters * counters,
- ExtVacReport * report)
+ PgStat_CommonCounts * report)
{
WalUsage walusage;
BufferUsage bufusage;
@@ -544,6 +548,11 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
long secs;
int usecs;
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ memset(report, 0, sizeof(PgStat_CommonCounts));
+
/* Calculate diffs of global stat parameters on WAL and buffer usage. */
memset(&walusage, 0, sizeof(WalUsage));
WalUsageAccumDiff(&walusage, &pgWalUsage, &counters->walusage);
@@ -592,6 +601,9 @@ void
extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
LVExtStatCountersIdx * counters)
{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
/* Set initial values for common heap and index statistics */
extvac_stats_start(rel, &counters->common);
counters->pages_deleted = counters->tuples_removed = 0;
@@ -609,11 +621,15 @@ extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
void
extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
- LVExtStatCountersIdx * counters, ExtVacReport * report)
+ LVExtStatCountersIdx * counters, PgStat_VacuumRelationCounts * report)
{
- memset(report, 0, sizeof(ExtVacReport));
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ memset(report, 0, sizeof(PgStat_VacuumRelationCounts));
+
+ extvac_stats_end(rel, &counters->common, &report->common);
- extvac_stats_end(rel, &counters->common, report);
report->type = PGSTAT_EXTVAC_INDEX;
if (stats != NULL)
@@ -624,7 +640,7 @@ extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
*/
/* Fill index-specific extended stats fields */
- report->tuples_deleted =
+ report->common.tuples_deleted =
stats->tuples_removed - counters->tuples_removed;
report->index.pages_deleted =
stats->pages_deleted - counters->pages_deleted;
@@ -647,8 +663,11 @@ extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
* procudure.
*/
static void
-accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats)
+accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacStats)
{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
/* Fill heap-specific extended stats fields */
extVacStats->type = PGSTAT_EXTVAC_TABLE;
extVacStats->table.pages_scanned = vacrel->scanned_pages;
@@ -656,46 +675,45 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats
extVacStats->table.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
extVacStats->table.vm_new_visible_pages = vacrel->vm_new_visible_pages;
extVacStats->table.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
- extVacStats->tuples_deleted = vacrel->tuples_deleted;
+ extVacStats->common.tuples_deleted = vacrel->tuples_deleted;
extVacStats->table.tuples_frozen = vacrel->tuples_frozen;
extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
- extVacStats->wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+ extVacStats->common.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
- extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
- extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
- extVacStats->total_blks_dirtied -= vacrel->extVacReportIdx.total_blks_dirtied;
- extVacStats->total_blks_hit -= vacrel->extVacReportIdx.total_blks_hit;
- extVacStats->total_blks_read -= vacrel->extVacReportIdx.total_blks_read;
- extVacStats->total_blks_written -= vacrel->extVacReportIdx.total_blks_written;
- extVacStats->wal_bytes -= vacrel->extVacReportIdx.wal_bytes;
- extVacStats->wal_fpi -= vacrel->extVacReportIdx.wal_fpi;
- extVacStats->wal_records -= vacrel->extVacReportIdx.wal_records;
+ extVacStats->common.blk_read_time -= vacrel->extVacReportIdx.common.blk_read_time;
+ extVacStats->common.blk_write_time -= vacrel->extVacReportIdx.common.blk_write_time;
+ extVacStats->common.total_blks_dirtied -= vacrel->extVacReportIdx.common.total_blks_dirtied;
+ extVacStats->common.total_blks_hit -= vacrel->extVacReportIdx.common.total_blks_hit;
+ extVacStats->common.total_blks_read -= vacrel->extVacReportIdx.common.total_blks_read;
+ extVacStats->common.total_blks_written -= vacrel->extVacReportIdx.common.total_blks_written;
+ extVacStats->common.wal_bytes -= vacrel->extVacReportIdx.common.wal_bytes;
+ extVacStats->common.wal_fpi -= vacrel->extVacReportIdx.common.wal_fpi;
+ extVacStats->common.wal_records -= vacrel->extVacReportIdx.common.wal_records;
- extVacStats->total_time -= vacrel->extVacReportIdx.total_time;
- extVacStats->delay_time -= vacrel->extVacReportIdx.delay_time;
+ extVacStats->common.total_time -= vacrel->extVacReportIdx.common.total_time;
+ extVacStats->common.delay_time -= vacrel->extVacReportIdx.common.delay_time;
}
static void
-accumulate_idxs_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacIdxStats)
+accumulate_idxs_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacIdxStats)
{
/* Fill heap-specific extended stats fields */
- vacrel->extVacReportIdx.blk_read_time += extVacIdxStats->blk_read_time;
- vacrel->extVacReportIdx.blk_write_time += extVacIdxStats->blk_write_time;
- vacrel->extVacReportIdx.total_blks_dirtied += extVacIdxStats->total_blks_dirtied;
- vacrel->extVacReportIdx.total_blks_hit += extVacIdxStats->total_blks_hit;
- vacrel->extVacReportIdx.total_blks_read += extVacIdxStats->total_blks_read;
- vacrel->extVacReportIdx.total_blks_written += extVacIdxStats->total_blks_written;
- vacrel->extVacReportIdx.wal_bytes += extVacIdxStats->wal_bytes;
- vacrel->extVacReportIdx.wal_fpi += extVacIdxStats->wal_fpi;
- vacrel->extVacReportIdx.wal_records += extVacIdxStats->wal_records;
- vacrel->extVacReportIdx.delay_time += extVacIdxStats->delay_time;
-
- vacrel->extVacReportIdx.total_time += extVacIdxStats->total_time;
+ vacrel->extVacReportIdx.common.blk_read_time += extVacIdxStats->common.blk_read_time;
+ vacrel->extVacReportIdx.common.blk_write_time += extVacIdxStats->common.blk_write_time;
+ vacrel->extVacReportIdx.common.total_blks_dirtied += extVacIdxStats->common.total_blks_dirtied;
+ vacrel->extVacReportIdx.common.total_blks_hit += extVacIdxStats->common.total_blks_hit;
+ vacrel->extVacReportIdx.common.total_blks_read += extVacIdxStats->common.total_blks_read;
+ vacrel->extVacReportIdx.common.total_blks_written += extVacIdxStats->common.total_blks_written;
+ vacrel->extVacReportIdx.common.wal_bytes += extVacIdxStats->common.wal_bytes;
+ vacrel->extVacReportIdx.common.wal_fpi += extVacIdxStats->common.wal_fpi;
+ vacrel->extVacReportIdx.common.wal_records += extVacIdxStats->common.wal_records;
+ vacrel->extVacReportIdx.common.delay_time += extVacIdxStats->common.delay_time;
+ vacrel->extVacReportIdx.common.total_time += extVacIdxStats->common.total_time;
}
@@ -856,10 +874,10 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
ErrorContextCallback errcallback;
char **indnames = NULL;
LVExtStatCounters extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
/* Initialize vacuum statistics */
- memset(&extVacReport, 0, sizeof(ExtVacReport));
+ memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts));
verbose = (params.options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -915,7 +933,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
- memset(&vacrel->extVacReportIdx, 0, sizeof(ExtVacReport));
+ memset(&vacrel->extVacReportIdx, 0, sizeof(PgStat_VacuumRelationCounts));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
/* Set up high level stuff about rel and its indexes */
vacrel->rel = rel;
@@ -1173,7 +1192,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
&frozenxid_updated, &minmulti_updated, false);
/* Make generic extended vacuum stats report */
- extvac_stats_end(rel, &extVacCounters, &extVacReport);
+ /* extvac_stats_end(rel, &extVacCounters, &extVacReport.common); */
/*
* Report results to the cumulative stats system, too.
@@ -1190,14 +1209,14 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
* Make generic extended vacuum stats report and fill heap-specific
* extended stats fields.
*/
- extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport);
+ extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport.common);
accumulate_heap_vacuum_statistics(vacrel, &extVacReport);
+ pgstat_report_vacuum_extstats(vacrel->reloid, rel->rd_rel->relisshared, &extVacReport);
pgstat_report_vacuum(rel,
Max(vacrel->new_live_tuples, 0),
vacrel->recently_dead_tuples +
vacrel->missed_dead_tuples,
- starttime,
- &extVacReport);
+ starttime);
pgstat_progress_end_command();
if (instrument)
@@ -2902,9 +2921,9 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
else
{
LVExtStatCounters counters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
- memset(&extVacReport, 0, sizeof(ExtVacReport));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
extvac_stats_start(vacrel->rel, &counters);
@@ -2912,7 +2931,7 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples,
vacrel->num_index_scans);
- extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport.common);
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
/*
@@ -3345,9 +3364,9 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
else
{
LVExtStatCounters counters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
- memset(&extVacReport, 0, sizeof(ExtVacReport));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
extvac_stats_start(vacrel->rel, &counters);
@@ -3356,7 +3375,7 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
vacrel->num_index_scans,
estimated_count);
- extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport.common);
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
}
@@ -3384,7 +3403,10 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
LVExtStatCountersIdx extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
+
+ memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
/* Set initial statistics values to gather vacuum statistics for the index */
extvac_stats_start_idx(indrel, istat, &extVacCounters);
@@ -3421,8 +3443,7 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
if (!ParallelVacuumIsActive(vacrel))
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
- pgstat_report_vacuum(indrel,
- 0, 0, 0, &extVacReport);
+ pgstat_report_vacuum_extstats(vacrel->indoid, indrel->rd_rel->relisshared, &extVacReport);
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
@@ -3449,7 +3470,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
LVExtStatCountersIdx extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
/* Set initial statistics values to gather vacuum statistics for the index */
extvac_stats_start_idx(indrel, istat, &extVacCounters);
@@ -3485,8 +3506,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
if (!ParallelVacuumIsActive(vacrel))
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
- pgstat_report_vacuum(indrel,
- 0, 0, 0, &extVacReport);
+ pgstat_report_vacuum_extstats(RelationGetRelid(indrel), indrel->rd_rel->relisshared, &extVacReport);
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 265cc3e5fbf..680b76b8ef9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1883,6 +1883,7 @@ heap_drop_with_catalog(Oid relid)
/* ensure that stats are dropped if transaction commits */
pgstat_drop_relation(rel);
+ pgstat_vacuum_relation_delete_pending_cb(RelationGetRelid(rel));
/*
* Close relcache entry, but *keep* AccessExclusiveLock on the relation
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8dea58ad96b..e906f9e1856 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2327,6 +2327,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
/* ensure that stats are dropped if transaction commits */
pgstat_drop_relation(userIndexRelation);
+ pgstat_vacuum_relation_delete_pending_cb(RelationGetRelid(userIndexRelation));
/*
* Close and flush the index's relcache entry, to ensure relcache doesn't
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index dc86b1ee212..4ec2a7d9f10 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1462,99 +1462,104 @@ GRANT EXECUTE ON FUNCTION pg_get_aios() TO pg_read_all_stats;
--
CREATE VIEW pg_stat_vacuum_tables AS
-SELECT
- ns.nspname AS schemaname,
- rel.relname AS relname,
- stats.relid as relid,
-
- stats.total_blks_read AS total_blks_read,
- stats.total_blks_hit AS total_blks_hit,
- stats.total_blks_dirtied AS total_blks_dirtied,
- stats.total_blks_written AS total_blks_written,
-
- stats.rel_blks_read AS rel_blks_read,
- stats.rel_blks_hit AS rel_blks_hit,
-
- stats.pages_scanned AS pages_scanned,
- stats.pages_removed AS pages_removed,
- stats.vm_new_frozen_pages AS vm_new_frozen_pages,
- stats.vm_new_visible_pages AS vm_new_visible_pages,
- stats.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
- stats.missed_dead_pages AS missed_dead_pages,
- stats.tuples_deleted AS tuples_deleted,
- stats.tuples_frozen AS tuples_frozen,
- stats.recently_dead_tuples AS recently_dead_tuples,
- stats.missed_dead_tuples AS missed_dead_tuples,
-
- stats.wraparound_failsafe AS wraparound_failsafe,
- stats.index_vacuum_count AS index_vacuum_count,
- stats.wal_records AS wal_records,
- stats.wal_fpi AS wal_fpi,
- stats.wal_bytes AS wal_bytes,
-
- stats.blk_read_time AS blk_read_time,
- stats.blk_write_time AS blk_write_time,
-
- stats.delay_time AS delay_time,
- stats.total_time AS total_time
-
-FROM pg_class rel
- JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
- LATERAL pg_stat_get_vacuum_tables(rel.oid) stats
-WHERE rel.relkind = 'r';
+ SELECT
+ N.nspname AS schemaname,
+ C.relname AS relname,
+ S.relid as relid,
+
+ S.total_blks_read AS total_blks_read,
+ S.total_blks_hit AS total_blks_hit,
+ S.total_blks_dirtied AS total_blks_dirtied,
+ S.total_blks_written AS total_blks_written,
+
+ S.rel_blks_read AS rel_blks_read,
+ S.rel_blks_hit AS rel_blks_hit,
+
+ S.pages_scanned AS pages_scanned,
+ S.pages_removed AS pages_removed,
+ S.vm_new_frozen_pages AS vm_new_frozen_pages,
+ S.vm_new_visible_pages AS vm_new_visible_pages,
+ S.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
+ S.missed_dead_pages AS missed_dead_pages,
+ S.tuples_deleted AS tuples_deleted,
+ S.tuples_frozen AS tuples_frozen,
+ S.recently_dead_tuples AS recently_dead_tuples,
+ S.missed_dead_tuples AS missed_dead_tuples,
+
+ S.wraparound_failsafe AS wraparound_failsafe,
+ S.index_vacuum_count AS index_vacuum_count,
+ S.wal_records AS wal_records,
+ S.wal_fpi AS wal_fpi,
+ S.wal_bytes AS wal_bytes,
+
+ S.blk_read_time AS blk_read_time,
+ S.blk_write_time AS blk_write_time,
+
+ S.delay_time AS delay_time,
+ S.total_time AS total_time
+
+ FROM pg_class C JOIN
+ pg_namespace N ON N.oid = C.relnamespace,
+ LATERAL pg_stat_get_vacuum_tables(C.oid) S
+ WHERE C.relkind IN ('r', 't', 'm');
CREATE VIEW pg_stat_vacuum_indexes AS
-SELECT
- rel.oid as relid,
- ns.nspname AS schemaname,
- rel.relname AS relname,
+ SELECT
+ C.oid AS relid,
+ I.oid AS indexrelid,
+ N.nspname AS schemaname,
+ C.relname AS relname,
+ I.relname AS indexrelname,
- total_blks_read AS total_blks_read,
- total_blks_hit AS total_blks_hit,
- total_blks_dirtied AS total_blks_dirtied,
- total_blks_written AS total_blks_written,
+ S.total_blks_read AS total_blks_read,
+ S.total_blks_hit AS total_blks_hit,
+ S.total_blks_dirtied AS total_blks_dirtied,
+ S.total_blks_written AS total_blks_written,
- rel_blks_read AS rel_blks_read,
- rel_blks_hit AS rel_blks_hit,
+ S.rel_blks_read AS rel_blks_read,
+ S.rel_blks_hit AS rel_blks_hit,
- pages_deleted AS pages_deleted,
- tuples_deleted AS tuples_deleted,
+ S.pages_deleted AS pages_deleted,
+ S.tuples_deleted AS tuples_deleted,
- wal_records AS wal_records,
- wal_fpi AS wal_fpi,
- wal_bytes AS wal_bytes,
+ S.wal_records AS wal_records,
+ S.wal_fpi AS wal_fpi,
+ S.wal_bytes AS wal_bytes,
- blk_read_time AS blk_read_time,
- blk_write_time AS blk_write_time,
+ S.blk_read_time AS blk_read_time,
+ S.blk_write_time AS blk_write_time,
- delay_time AS delay_time,
- total_time AS total_time
-FROM
- pg_class rel
- JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
- LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
-WHERE rel.relkind = 'i';
+ S.delay_time AS delay_time,
+ S.total_time AS total_time
+ FROM
+ pg_class C JOIN
+ pg_index X ON C.oid = X.indrelid JOIN
+ pg_class I ON I.oid = X.indexrelid
+ LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace),
+ LATERAL pg_stat_get_vacuum_indexes(I.oid) S
+ WHERE C.relkind IN ('r', 't', 'm');
CREATE VIEW pg_stat_vacuum_database AS
-SELECT
- db.oid as dboid,
- db.datname AS dbname,
-
- stats.db_blks_read AS db_blks_read,
- stats.db_blks_hit AS db_blks_hit,
- stats.total_blks_dirtied AS total_blks_dirtied,
- stats.total_blks_written AS total_blks_written,
-
- stats.wal_records AS wal_records,
- stats.wal_fpi AS wal_fpi,
- stats.wal_bytes AS wal_bytes,
-
- stats.blk_read_time AS blk_read_time,
- stats.blk_write_time AS blk_write_time,
-
- stats.delay_time AS delay_time,
- stats.total_time AS total_time,
- stats.wraparound_failsafe AS wraparound_failsafe
-FROM
- pg_database db,
- LATERAL pg_stat_get_vacuum_database(db.oid) stats;
\ No newline at end of file
+ SELECT
+ D.oid as dboid,
+ D.datname AS dbname,
+
+ S.db_blks_read AS db_blks_read,
+ S.db_blks_hit AS db_blks_hit,
+ S.total_blks_dirtied AS total_blks_dirtied,
+ S.total_blks_written AS total_blks_written,
+
+ S.wal_records AS wal_records,
+ S.wal_fpi AS wal_fpi,
+ S.wal_bytes AS wal_bytes,
+
+ S.blk_read_time AS blk_read_time,
+ S.blk_write_time AS blk_write_time,
+
+ S.delay_time AS delay_time,
+ S.total_time AS total_time,
+ S.wraparound_failsafe AS wraparound_failsafe,
+ S.errors AS errors
+ FROM
+ pg_database D,
+ LATERAL pg_stat_get_vacuum_database(D.oid) S;
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index d1f3be89b35..bf3cd3b1cc9 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -1815,6 +1815,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
* Tell the cumulative stats system to forget it immediately, too.
*/
pgstat_drop_database(db_id);
+ pgstat_drop_vacuum_database(db_id);
/*
* Except for the deletion of the catalog row, subsequent actions are not
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 43450685b09..c7dd2bb52f6 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -869,7 +869,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
IndexBulkDeleteResult *istat_res;
IndexVacuumInfo ivinfo;
LVExtStatCountersIdx extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
/*
* Update the pointer to the corresponding bulk-deletion result if someone
@@ -911,8 +911,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
/* Make extended vacuum stats report for index */
extvac_stats_end_idx(indrel, istat_res, &extVacCounters, &extVacReport);
- pgstat_report_vacuum(indrel,
- 0, 0, 0, &extVacReport);
+ pgstat_report_vacuum_extstats(RelationGetRelid(indrel), indrel->rd_rel->relisshared, &extVacReport);
/*
* Copy the index bulk-deletion result returned from ambulkdelete and
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 9c2443e1ecd..183f7514d2d 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -27,6 +27,7 @@ OBJS = \
pgstat_function.o \
pgstat_io.o \
pgstat_relation.o \
+ pgstat_vacuum.o \
pgstat_replslot.o \
pgstat_shmem.o \
pgstat_slru.o \
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index f317c6e8e90..cdc9cab01cf 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -203,7 +203,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind);
bool pgstat_track_counts = false;
int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE;
-
+bool pgstat_track_vacuum_statistics = false;
/* ----------
* state shared with pgstat_*.c
@@ -482,6 +482,34 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
.reset_all_cb = pgstat_wal_reset_all_cb,
.snapshot_cb = pgstat_wal_snapshot_cb,
},
+ [PGSTAT_KIND_VACUUM_DB] = {
+ .name = "vacuum statistics",
+
+ .fixed_amount = false,
+ .write_to_file = true,
+ /* so pg_stat_database entries can be seen in all databases */
+ .accessed_across_databases = true,
+
+ .shared_size = sizeof(PgStatShared_VacuumDB),
+ .shared_data_off = offsetof(PgStatShared_VacuumDB, stats),
+ .shared_data_len = sizeof(((PgStatShared_VacuumDB *) 0)->stats),
+ .pending_size = sizeof(PgStat_VacuumDBCounts),
+
+ .flush_pending_cb = pgstat_vacuum_db_flush_cb,
+ },
+ [PGSTAT_KIND_VACUUM_RELATION] = {
+ .name = "vacuum statistics",
+
+ .fixed_amount = false,
+ .write_to_file = true,
+
+ .shared_size = sizeof(PgStatShared_VacuumRelation),
+ .shared_data_off = offsetof(PgStatShared_VacuumRelation, stats),
+ .shared_data_len = sizeof(((PgStatShared_VacuumRelation *) 0)->stats),
+ .pending_size = sizeof(PgStat_RelationVacuumPending),
+
+ .flush_pending_cb = pgstat_vacuum_relation_flush_cb
+ },
};
/*
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 65207d30378..80e6c7c229a 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -46,6 +46,15 @@ pgstat_drop_database(Oid databaseid)
pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
}
+/*
+ * Remove entry for the database being dropped.
+ */
+void
+pgstat_drop_vacuum_database(Oid databaseid)
+{
+ pgstat_drop_transactional(PGSTAT_KIND_VACUUM_DB, databaseid, InvalidOid);
+}
+
/*
* Called from autovacuum.c to report startup of an autovacuum process.
* We are called before InitPostgres is done, so can't rely on MyDatabaseId;
@@ -485,7 +494,6 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
pgstat_unlock_entry(entry_ref);
memset(pendingent, 0, sizeof(*pendingent));
- memset(&(pendingent)->vacuum_ext, 0, sizeof(ExtVacReport));
return true;
}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 2675c541369..e8665d23099 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -47,8 +47,6 @@ static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_lev
static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
-static void pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
- bool accumulate_reltype_specific_info);
/*
@@ -210,12 +208,11 @@ pgstat_drop_relation(Relation rel)
*/
void
pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
- PgStat_Counter deadtuples, TimestampTz starttime, ExtVacReport * params)
+ PgStat_Counter deadtuples, TimestampTz starttime)
{
PgStat_EntryRef *entry_ref;
PgStatShared_Relation *shtabentry;
PgStat_StatTabEntry *tabentry;
- PgStatShared_Database *dbentry;
Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
TimestampTz ts;
PgStat_Counter elapsedtime;
@@ -237,8 +234,6 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
tabentry->live_tuples = livetuples;
tabentry->dead_tuples = deadtuples;
- pgstat_accumulate_extvac_stats(&tabentry->vacuum_ext, params, true);
-
/*
* It is quite possible that a non-aggressive VACUUM ended up skipping
* various pages, however, we'll zero the insert counter here regardless.
@@ -274,16 +269,6 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
*/
pgstat_flush_io(false);
(void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
-
- if (dboid != InvalidOid)
- {
- entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
- dboid, InvalidOid, false);
- dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
-
- pgstat_accumulate_extvac_stats(&dbentry->stats.vacuum_ext, params, false);
- pgstat_unlock_entry(entry_ref);
- }
}
/*
@@ -918,6 +903,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
return true;
}
+void
+pgstat_vacuum_relation_delete_pending_cb(Oid relid)
+{
+ pgstat_drop_transactional(PGSTAT_KIND_VACUUM_RELATION, relid, InvalidOid);
+}
+
void
pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
{
@@ -1027,55 +1018,3 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
trans->tuples_deleted = trans->deleted_pre_truncdrop;
}
}
-
-static void
-pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
- bool accumulate_reltype_specific_info)
-{
- dst->total_blks_read += src->total_blks_read;
- dst->total_blks_hit += src->total_blks_hit;
- dst->total_blks_dirtied += src->total_blks_dirtied;
- dst->total_blks_written += src->total_blks_written;
- dst->wal_bytes += src->wal_bytes;
- dst->wal_fpi += src->wal_fpi;
- dst->wal_records += src->wal_records;
- dst->blk_read_time += src->blk_read_time;
- dst->blk_write_time += src->blk_write_time;
- dst->delay_time += src->delay_time;
- dst->total_time += src->total_time;
- dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
-
- if (!accumulate_reltype_specific_info)
- return;
-
- if (dst->type == PGSTAT_EXTVAC_INVALID)
- dst->type = src->type;
-
- Assert(src->type == PGSTAT_EXTVAC_INVALID || src->type == dst->type);
-
- if (dst->type == src->type)
- {
- dst->blks_fetched += src->blks_fetched;
- dst->blks_hit += src->blks_hit;
-
- if (dst->type == PGSTAT_EXTVAC_TABLE)
- {
- dst->table.pages_scanned += src->table.pages_scanned;
- dst->table.pages_removed += src->table.pages_removed;
- dst->table.vm_new_frozen_pages += src->table.vm_new_frozen_pages;
- dst->table.vm_new_visible_pages += src->table.vm_new_visible_pages;
- dst->table.vm_new_visible_frozen_pages += src->table.vm_new_visible_frozen_pages;
- dst->tuples_deleted += src->tuples_deleted;
- dst->table.tuples_frozen += src->table.tuples_frozen;
- dst->table.recently_dead_tuples += src->table.recently_dead_tuples;
- dst->table.index_vacuum_count += src->table.index_vacuum_count;
- dst->table.missed_dead_pages += src->table.missed_dead_pages;
- dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
- }
- else if (dst->type == PGSTAT_EXTVAC_INDEX)
- {
- dst->index.pages_deleted += src->index.pages_deleted;
- dst->tuples_deleted += src->tuples_deleted;
- }
- }
-}
diff --git a/src/backend/utils/activity/pgstat_vacuum.c b/src/backend/utils/activity/pgstat_vacuum.c
new file mode 100644
index 00000000000..340ee24f26a
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_vacuum.c
@@ -0,0 +1,214 @@
+#include "postgres.h"
+
+#include "pgstat.h"
+#include "utils/pgstat_internal.h"
+#include "utils/memutils.h"
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool pgstat_track_vacuum_statistics_for_relations = false;
+
+#define ACCUMULATE_FIELD(field) dst->field += src->field;
+
+#define ACCUMULATE_SUBFIELD(substruct, field) \
+ (dst->substruct.field += src->substruct.field)
+
+static void
+pgstat_accumulate_common(PgStat_CommonCounts * dst, const PgStat_CommonCounts * src)
+{
+ ACCUMULATE_FIELD(total_blks_read);
+ ACCUMULATE_FIELD(total_blks_hit);
+ ACCUMULATE_FIELD(total_blks_dirtied);
+ ACCUMULATE_FIELD(total_blks_written);
+
+ ACCUMULATE_FIELD(blks_fetched);
+ ACCUMULATE_FIELD(blks_hit);
+
+ ACCUMULATE_FIELD(wal_records);
+ ACCUMULATE_FIELD(wal_fpi);
+ ACCUMULATE_FIELD(wal_bytes);
+
+ ACCUMULATE_FIELD(blk_read_time);
+ ACCUMULATE_FIELD(blk_write_time);
+ ACCUMULATE_FIELD(delay_time);
+ ACCUMULATE_FIELD(total_time);
+
+ ACCUMULATE_FIELD(tuples_deleted);
+ ACCUMULATE_FIELD(wraparound_failsafe_count);
+}
+
+static void
+pgstat_accumulate_extvac_stats_relations(PgStat_VacuumRelationCounts * dst, PgStat_VacuumRelationCounts * src)
+{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ if (dst->type == PGSTAT_EXTVAC_INVALID)
+ dst->type = src->type;
+
+ Assert(src->type != PGSTAT_EXTVAC_INVALID && src->type != PGSTAT_EXTVAC_DB && src->type == dst->type);
+
+ pgstat_accumulate_common(&dst->common, &src->common);
+
+ ACCUMULATE_SUBFIELD(common, blks_fetched);
+ ACCUMULATE_SUBFIELD(common, blks_hit);
+
+ if (dst->type == PGSTAT_EXTVAC_TABLE)
+ {
+ ACCUMULATE_SUBFIELD(common, tuples_deleted);
+ ACCUMULATE_SUBFIELD(table, pages_scanned);
+ ACCUMULATE_SUBFIELD(table, pages_removed);
+ ACCUMULATE_SUBFIELD(table, vm_new_frozen_pages);
+ ACCUMULATE_SUBFIELD(table, vm_new_visible_pages);
+ ACCUMULATE_SUBFIELD(table, vm_new_visible_frozen_pages);
+ ACCUMULATE_SUBFIELD(table, tuples_frozen);
+ ACCUMULATE_SUBFIELD(table, recently_dead_tuples);
+ ACCUMULATE_SUBFIELD(table, index_vacuum_count);
+ ACCUMULATE_SUBFIELD(table, missed_dead_pages);
+ ACCUMULATE_SUBFIELD(table, missed_dead_tuples);
+ }
+ else if (dst->type == PGSTAT_EXTVAC_INDEX)
+ {
+ ACCUMULATE_SUBFIELD(common, tuples_deleted);
+ ACCUMULATE_SUBFIELD(index, pages_deleted);
+ }
+}
+
+static void
+pgstat_accumulate_extvac_stats_db(PgStat_VacuumDBCounts * dst, PgStat_VacuumDBCounts * src)
+{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ pgstat_accumulate_common(&dst->common, &src->common);
+}
+
+/*
+ * Report that the table was just vacuumed and flush statistics.
+ */
+void
+pgstat_report_vacuum_extstats(Oid tableoid, bool shared,
+ PgStat_VacuumRelationCounts * params)
+{
+ PgStat_EntryRef *entry_ref;
+ PgStatShared_VacuumRelation *shtabentry;
+ PgStatShared_VacuumDB *shdbentry;
+ Oid dboid = (shared ? InvalidOid : MyDatabaseId);
+
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_VACUUM_RELATION,
+ dboid, tableoid, false);
+ shtabentry = (PgStatShared_VacuumRelation *) entry_ref->shared_stats;
+ pgstat_accumulate_extvac_stats_relations(&shtabentry->stats, params);
+
+ pgstat_unlock_entry(entry_ref);
+
+
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_VACUUM_DB,
+ dboid, InvalidOid, false);
+
+ shdbentry = (PgStatShared_VacuumDB *) entry_ref->shared_stats;
+
+ pgstat_accumulate_common(&shdbentry->stats.common, ¶ms->common);
+
+ pgstat_unlock_entry(entry_ref);
+}
+
+/*
+ * Flush out pending stats for the entry
+ *
+ * If nowait is true, this function returns false if lock could not
+ * immediately acquired, otherwise true is returned.
+ */
+bool
+pgstat_vacuum_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+ PgStatShared_VacuumRelation *shtabstats;
+ PgStat_RelationVacuumPending *pendingent; /* table entry of shared stats */
+
+ pendingent = (PgStat_RelationVacuumPending *) entry_ref->pending;
+ shtabstats = (PgStatShared_VacuumRelation *) entry_ref->shared_stats;
+
+ /*
+ * Ignore entries that didn't accumulate any actual counts.
+ */
+ if (pg_memory_is_all_zeros(&pendingent,
+ sizeof(struct PgStat_RelationVacuumPending)))
+ return true;
+
+ if (!pgstat_lock_entry(entry_ref, nowait))
+ {
+ return false;
+ }
+
+ pgstat_accumulate_extvac_stats_relations(&(shtabstats->stats), &(pendingent->counts));
+
+ pgstat_unlock_entry(entry_ref);
+
+ return true;
+}
+
+/*
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * the vacuum collected statistics for one relation or NULL.
+ */
+PgStat_VacuumRelationCounts *
+pgstat_fetch_stat_vacuum_tabentry(Oid relid, Oid dbid)
+{
+ return (PgStat_VacuumRelationCounts *)
+ pgstat_fetch_entry(PGSTAT_KIND_VACUUM_RELATION, dbid, relid);
+}
+
+PgStat_VacuumDBCounts *
+pgstat_fetch_stat_vacuum_dbentry(Oid dbid)
+{
+ return (PgStat_VacuumDBCounts *)
+ pgstat_fetch_entry(PGSTAT_KIND_VACUUM_DB, dbid, InvalidOid);
+}
+
+bool
+pgstat_vacuum_db_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+ PgStatShared_VacuumDB *sharedent;
+ PgStat_VacuumDBCounts *pendingent;
+
+ pendingent = (PgStat_VacuumDBCounts *) entry_ref->pending;
+ sharedent = (PgStatShared_VacuumDB *) entry_ref->shared_stats;
+
+ if (!pgstat_lock_entry(entry_ref, nowait))
+ return false;
+
+ /* The entry was successfully flushed, add the same to database stats */
+ pgstat_accumulate_extvac_stats_db(&(sharedent->stats), pendingent);
+
+ pgstat_unlock_entry(entry_ref);
+
+ return true;
+}
+
+/*
+ * Find or create a local PgStat_VacuumDBCounts entry for dboid.
+ */
+PgStat_VacuumDBCounts *
+pgstat_prep_vacuum_database_pending(Oid dboid)
+{
+ PgStat_EntryRef *entry_ref;
+
+ /*
+ * This should not report stats on database objects before having
+ * connected to a database.
+ */
+ Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
+
+ entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_VACUUM_DB, dboid, InvalidOid,
+ NULL);
+
+ if (entry_ref == NULL)
+ return NULL;
+
+ return entry_ref->pending;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 4e2714f2e6a..0a64f034a3f 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2314,7 +2314,6 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objid));
}
-
/*
* Get the vacuum statistics for the heap tables.
*/
@@ -2324,41 +2323,45 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 26
Oid relid = PG_GETARG_OID(0);
- PgStat_StatTabEntry *tabentry;
- ExtVacReport *extvacuum;
+ PgStat_VacuumRelationCounts *extvacuum;
+ PgStat_VacuumRelationCounts *pending;
TupleDesc tupdesc;
Datum values[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
bool nulls[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
char buf[256];
int i = 0;
+ /* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
- tabentry = pgstat_fetch_stat_tabentry(relid);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, MyDatabaseId);
- if (!tabentry)
- {
- InitMaterializedSRF(fcinfo, 0);
- PG_RETURN_VOID();
- }
- else
+ if (!pending)
{
- extvacuum = &(tabentry->vacuum_ext);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, 0);
+
+ if (!pending)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
}
+ extvacuum = pending;
+
i = 0;
values[i++] = ObjectIdGetDatum(relid);
- values[i++] = Int64GetDatum(extvacuum->total_blks_read);
- values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
- values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
- values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
- values[i++] = Int64GetDatum(extvacuum->blks_fetched -
- extvacuum->blks_hit);
- values[i++] = Int64GetDatum(extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_fetched -
+ extvacuum->common.blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_hit);
values[i++] = Int64GetDatum(extvacuum->table.pages_scanned);
values[i++] = Int64GetDatum(extvacuum->table.pages_removed);
@@ -2366,28 +2369,28 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_pages);
values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_frozen_pages);
values[i++] = Int64GetDatum(extvacuum->table.missed_dead_pages);
- values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+ values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
values[i++] = Int64GetDatum(extvacuum->table.tuples_frozen);
values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
- values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+ values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
- values[i++] = Int64GetDatum(extvacuum->wal_records);
- values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
/* Convert to numeric, like pg_stat_statements */
- snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
values[i++] = DirectFunctionCall3(numeric_in,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- values[i++] = Float8GetDatum(extvacuum->blk_read_time);
- values[i++] = Float8GetDatum(extvacuum->blk_write_time);
- values[i++] = Float8GetDatum(extvacuum->delay_time);
- values[i++] = Float8GetDatum(extvacuum->total_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+ values[i++] = Float8GetDatum(extvacuum->common.total_time);
Assert(i == PG_STAT_GET_VACUUM_TABLES_STATS_COLS);
@@ -2404,8 +2407,8 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 16
Oid relid = PG_GETARG_OID(0);
- PgStat_StatTabEntry *tabentry;
- ExtVacReport *extvacuum;
+ PgStat_VacuumRelationCounts *extvacuum;
+ PgStat_VacuumRelationCounts *pending;
TupleDesc tupdesc;
Datum values[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
bool nulls[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
@@ -2415,48 +2418,51 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
- tabentry = pgstat_fetch_stat_tabentry(relid);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, MyDatabaseId);
- if (tabentry == NULL)
- {
- InitMaterializedSRF(fcinfo, 0);
- PG_RETURN_VOID();
- }
- else
+ if (!pending)
{
- extvacuum = &(tabentry->vacuum_ext);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, 0);
+
+ if (!pending)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
}
+ extvacuum = pending;
+
i = 0;
values[i++] = ObjectIdGetDatum(relid);
- values[i++] = Int64GetDatum(extvacuum->total_blks_read);
- values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
- values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
- values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
- values[i++] = Int64GetDatum(extvacuum->blks_fetched -
- extvacuum->blks_hit);
- values[i++] = Int64GetDatum(extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_fetched -
+ extvacuum->common.blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_hit);
values[i++] = Int64GetDatum(extvacuum->index.pages_deleted);
- values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+ values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
- values[i++] = Int64GetDatum(extvacuum->wal_records);
- values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
/* Convert to numeric, like pg_stat_statements */
- snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
values[i++] = DirectFunctionCall3(numeric_in,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- values[i++] = Float8GetDatum(extvacuum->blk_read_time);
- values[i++] = Float8GetDatum(extvacuum->blk_write_time);
- values[i++] = Float8GetDatum(extvacuum->delay_time);
- values[i++] = Float8GetDatum(extvacuum->total_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+ values[i++] = Float8GetDatum(extvacuum->common.total_time);
Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS);
@@ -2470,8 +2476,8 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 14
Oid dbid = PG_GETARG_OID(0);
- PgStat_StatDBEntry *dbentry;
- ExtVacReport *extvacuum;
+ PgStat_VacuumDBCounts *extvacuum;
+ PgStat_VacuumDBCounts *pending;
TupleDesc tupdesc;
Datum values[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
bool nulls[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
@@ -2481,42 +2487,41 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
- dbentry = pgstat_fetch_stat_dbentry(dbid);
+ pending = pgstat_fetch_stat_vacuum_dbentry(dbid);
- if (dbentry == NULL)
+ if (!pending)
{
InitMaterializedSRF(fcinfo, 0);
PG_RETURN_VOID();
}
- else
- {
- extvacuum = &(dbentry->vacuum_ext);
- }
+
+ extvacuum = pending;
i = 0;
values[i++] = ObjectIdGetDatum(dbid);
- values[i++] = Int64GetDatum(extvacuum->total_blks_read);
- values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
- values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
- values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
- values[i++] = Int64GetDatum(extvacuum->wal_records);
- values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
/* Convert to numeric, like pg_stat_statements */
- snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
values[i++] = DirectFunctionCall3(numeric_in,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- values[i++] = Float8GetDatum(extvacuum->blk_read_time);
- values[i++] = Float8GetDatum(extvacuum->blk_write_time);
- values[i++] = Float8GetDatum(extvacuum->delay_time);
- values[i++] = Float8GetDatum(extvacuum->total_time);
- values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+ values[i++] = Float8GetDatum(extvacuum->common.total_time);
+ values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
+ values[i++] = Int32GetDatum(extvacuum->errors);
Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..631df3a57c3 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3084,6 +3084,12 @@
boot_val => 'false',
},
+{ name => 'track_vacuum_statistics', type => 'bool', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
+ short_desc => 'Collects vacuum statistics for vacuum activity.',
+ variable => 'pgstat_track_vacuum_statistics',
+ boot_val => 'false',
+},
+
{ name => 'track_wal_io_timing', type => 'bool', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
short_desc => 'Collects timing statistics for WAL I/O activity.',
variable => 'track_wal_io_timing',
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index b48ace6084b..6e85b08aa89 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -437,5 +437,5 @@ extern double anl_get_next_S(double t, int n, double *stateptr);
extern void extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
LVExtStatCountersIdx * counters);
extern void extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
- LVExtStatCountersIdx * counters, ExtVacReport * report);
+ LVExtStatCountersIdx * counters, PgStat_VacuumRelationCounts * report);
#endif /* VACUUM_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f3bdc1c38df..61d488f1bf8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -119,54 +119,100 @@ typedef enum ExtVacReportType
{
PGSTAT_EXTVAC_INVALID = 0,
PGSTAT_EXTVAC_TABLE = 1,
- PGSTAT_EXTVAC_INDEX = 2
-} ExtVacReportType;
+ PGSTAT_EXTVAC_INDEX = 2,
+ PGSTAT_EXTVAC_DB = 3,
+} ExtVacReportType;
/* ----------
+ * PgStat_TableCounts The actual per-table counts kept by a backend
*
- * ExtVacReport
+ * This struct should contain only actual event counters, because we make use
+ * of pg_memory_is_all_zeros() to detect whether there are any stats updates
+ * to apply.
*
- * Additional statistics of vacuum processing over a relation.
- * pages_removed is the amount by which the physically shrank,
- * if any (ie the change in its total size on disk)
- * pages_deleted refer to free space within the index file
+ * It is a component of PgStat_TableStatus (within-backend state).
+ *
+ * Note: for a table, tuples_returned is the number of tuples successfully
+ * fetched by heap_getnext, while tuples_fetched is the number of tuples
+ * successfully fetched by heap_fetch under the control of bitmap indexscans.
+ * For an index, tuples_returned is the number of index entries returned by
+ * the index AM, while tuples_fetched is the number of tuples successfully
+ * fetched by heap_fetch under the control of simple indexscans for this index.
+ *
+ * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted
+ * actions, regardless of whether the transaction committed. delta_live_tuples,
+ * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
+ * Note that delta_live_tuples and delta_dead_tuples can be negative!
* ----------
*/
-typedef struct ExtVacReport
+typedef struct PgStat_TableCounts
{
- /*
- * number of blocks missed, hit, dirtied and written during a vacuum of
- * specific relation
- */
+ PgStat_Counter numscans;
+
+ PgStat_Counter tuples_returned;
+ PgStat_Counter tuples_fetched;
+
+ PgStat_Counter tuples_inserted;
+ PgStat_Counter tuples_updated;
+ PgStat_Counter tuples_deleted;
+ PgStat_Counter tuples_hot_updated;
+ PgStat_Counter tuples_newpage_updated;
+ bool truncdropped;
+
+ PgStat_Counter delta_live_tuples;
+ PgStat_Counter delta_dead_tuples;
+ PgStat_Counter changed_tuples;
+
+ PgStat_Counter blocks_fetched;
+ PgStat_Counter blocks_hit;
+
+ PgStat_Counter rev_all_visible_pages;
+ PgStat_Counter rev_all_frozen_pages;
+} PgStat_TableCounts;
+
+typedef struct PgStat_CommonCounts
+{
+ /* blocks */
int64 total_blks_read;
int64 total_blks_hit;
int64 total_blks_dirtied;
int64 total_blks_written;
- /*
- * blocks missed and hit for just the heap during a vacuum of specific
- * relation
- */
+ /* heap blocks */
int64 blks_fetched;
int64 blks_hit;
- /* Vacuum WAL usage stats */
- int64 wal_records; /* wal usage: number of WAL records */
- int64 wal_fpi; /* wal usage: number of WAL full page images
- * produced */
- uint64 wal_bytes; /* wal usage: size of WAL records produced */
+ /* WAL */
+ int64 wal_records;
+ int64 wal_fpi;
+ uint64 wal_bytes;
- /* Time stats. */
- double blk_read_time; /* time spent reading pages, in msec */
- double blk_write_time; /* time spent writing pages, in msec */
- double delay_time; /* how long vacuum slept in vacuum delay
- * point, in msec */
- double total_time; /* total time of a vacuum operation, in msec */
+ /* Time */
+ double blk_read_time;
+ double blk_write_time;
+ double delay_time;
+ double total_time;
- int64 tuples_deleted; /* tuples deleted by vacuum */
+ /* tuples */
+ int64 tuples_deleted;
- int32 wraparound_failsafe_count; /* the number of times to prevent
- * wraparound problem */
+ /* failsafe */
+ int32 wraparound_failsafe_count;
+} PgStat_CommonCounts;
+
+/* ----------
+ *
+ * PgStat_VacuumRelationCounts
+ *
+ * Additional statistics of vacuum processing over a relation.
+ * pages_removed is the amount by which the physically shrank,
+ * if any (ie the change in its total size on disk)
+ * pages_deleted refer to free space within the index file
+ * ----------
+ */
+typedef struct PgStat_VacuumRelationCounts
+{
+ PgStat_CommonCounts common;
ExtVacReportType type; /* heap, index, etc. */
@@ -185,6 +231,13 @@ typedef struct ExtVacReport
{
struct
{
+ int64 tuples_frozen; /* tuples frozen up by vacuum */
+ int64 recently_dead_tuples; /* deleted tuples that are
+ * still visible to some
+ * transaction */
+ int64 missed_dead_tuples; /* tuples not pruned by vacuum due
+ * to failure to get a cleanup
+ * lock */
int64 pages_scanned; /* heap pages examined (not skipped by
* VM) */
int64 pages_removed; /* heap pages removed by vacuum
@@ -192,10 +245,6 @@ typedef struct ExtVacReport
int64 pages_frozen; /* pages marked in VM as frozen */
int64 pages_all_visible; /* pages marked in VM as
* all-visible */
- int64 tuples_frozen; /* tuples frozen up by vacuum */
- int64 recently_dead_tuples; /* deleted tuples that are
- * still visible to some
- * transaction */
int64 vm_new_frozen_pages; /* pages marked in VM as
* frozen */
int64 vm_new_visible_pages; /* pages marked in VM as
@@ -203,9 +252,6 @@ typedef struct ExtVacReport
int64 vm_new_visible_frozen_pages; /* pages marked in VM as
* all-visible and
* frozen */
- int64 missed_dead_tuples; /* tuples not pruned by vacuum due
- * to failure to get a cleanup
- * lock */
int64 missed_dead_pages; /* pages with missed dead tuples */
int64 index_vacuum_count; /* number of index vacuumings */
} table;
@@ -214,60 +260,21 @@ typedef struct ExtVacReport
int64 pages_deleted; /* number of pages deleted by vacuum */
} index;
} /* per_type_stats */ ;
-} ExtVacReport;
+} PgStat_VacuumRelationCounts;
-/* ----------
- * PgStat_TableCounts The actual per-table counts kept by a backend
- *
- * This struct should contain only actual event counters, because we make use
- * of pg_memory_is_all_zeros() to detect whether there are any stats updates
- * to apply.
- *
- * It is a component of PgStat_TableStatus (within-backend state).
- *
- * Note: for a table, tuples_returned is the number of tuples successfully
- * fetched by heap_getnext, while tuples_fetched is the number of tuples
- * successfully fetched by heap_fetch under the control of bitmap indexscans.
- * For an index, tuples_returned is the number of index entries returned by
- * the index AM, while tuples_fetched is the number of tuples successfully
- * fetched by heap_fetch under the control of simple indexscans for this index.
- *
- * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted
- * actions, regardless of whether the transaction committed. delta_live_tuples,
- * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
- * Note that delta_live_tuples and delta_dead_tuples can be negative!
- * ----------
- */
-typedef struct PgStat_TableCounts
+typedef struct PgStat_VacuumRelationStatus
{
- PgStat_Counter numscans;
-
- PgStat_Counter tuples_returned;
- PgStat_Counter tuples_fetched;
-
- PgStat_Counter tuples_inserted;
- PgStat_Counter tuples_updated;
- PgStat_Counter tuples_deleted;
- PgStat_Counter tuples_hot_updated;
- PgStat_Counter tuples_newpage_updated;
- bool truncdropped;
-
- PgStat_Counter delta_live_tuples;
- PgStat_Counter delta_dead_tuples;
- PgStat_Counter changed_tuples;
-
- PgStat_Counter blocks_fetched;
- PgStat_Counter blocks_hit;
-
- PgStat_Counter rev_all_visible_pages;
- PgStat_Counter rev_all_frozen_pages;
+ Oid id; /* table's OID */
+ bool shared; /* is it a shared catalog? */
+ PgStat_VacuumRelationCounts counts; /* event counts to be sent */
+} PgStat_VacuumRelationStatus;
- /*
- * Additional cumulative stat on vacuum operations. Use an expensive
- * structure as an abstraction for different types of relations.
- */
- ExtVacReport vacuum_ext;
-} PgStat_TableCounts;
+typedef struct PgStat_VacuumDBCounts
+{
+ Oid dbjid;
+ PgStat_CommonCounts common;
+ int32 errors;
+} PgStat_VacuumDBCounts;
/* ----------
* PgStat_TableStatus Per-table status within a backend
@@ -293,6 +300,12 @@ typedef struct PgStat_TableStatus
Relation relation; /* rel that is using this entry */
} PgStat_TableStatus;
+typedef struct PgStat_RelationVacuumPending
+{
+ Oid id; /* table's OID */
+ PgStat_VacuumRelationCounts counts; /* event counts to be sent */
+} PgStat_RelationVacuumPending;
+
/* ----------
* PgStat_TableXactStatus Per-table, per-subtransaction status
* ----------
@@ -489,8 +502,6 @@ typedef struct PgStat_StatDBEntry
PgStat_Counter parallel_workers_launched;
TimestampTz stat_reset_timestamp;
-
- ExtVacReport vacuum_ext; /* extended vacuum statistics */
} PgStat_StatDBEntry;
typedef struct PgStat_StatFuncEntry
@@ -578,8 +589,6 @@ typedef struct PgStat_StatTabEntry
PgStat_Counter rev_all_visible_pages;
PgStat_Counter rev_all_frozen_pages;
-
- ExtVacReport vacuum_ext;
} PgStat_StatTabEntry;
/* ------
@@ -788,7 +797,7 @@ extern void pgstat_unlink_relation(Relation rel);
extern void pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
PgStat_Counter deadtuples,
- TimestampTz starttime, ExtVacReport * params);
+ TimestampTz starttime);
extern void pgstat_report_analyze(Relation rel,
PgStat_Counter livetuples, PgStat_Counter deadtuples,
bool resetcounter, TimestampTz starttime);
@@ -924,6 +933,16 @@ extern int pgstat_get_transactional_drops(bool isCommit, struct xl_xact_stats_it
extern void pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, bool is_redo);
+extern void pgstat_drop_vacuum_database(Oid databaseid);
+extern void pgstat_vacuum_relation_delete_pending_cb(Oid relid);
+extern void
+ pgstat_report_vacuum_extstats(Oid tableoid, bool shared,
+ PgStat_VacuumRelationCounts * params);
+extern PgStat_RelationVacuumPending * find_vacuum_relation_entry(Oid relid);
+extern PgStat_VacuumDBCounts * pgstat_prep_vacuum_database_pending(Oid dboid);
+extern PgStat_VacuumRelationCounts * pgstat_fetch_stat_vacuum_tabentry(Oid relid, Oid dbid);
+PgStat_VacuumDBCounts *pgstat_fetch_stat_vacuum_dbentry(Oid dbid);
+
/*
* Functions in pgstat_wal.c
*/
@@ -940,7 +959,8 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void);
extern PGDLLIMPORT bool pgstat_track_counts;
extern PGDLLIMPORT int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_fetch_consistency;
-
+extern PGDLLIMPORT bool pgstat_track_vacuum_statistics;
+extern PGDLLIMPORT bool pgstat_track_vacuum_statistics_for_relations;
/*
* Variables in pgstat_bgwriter.c
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 7dffab8dbdd..4abe70cb54e 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -500,6 +500,18 @@ typedef struct PgStatShared_Relation
PgStat_StatTabEntry stats;
} PgStatShared_Relation;
+typedef struct PgStatShared_VacuumDB
+{
+ PgStatShared_Common header;
+ PgStat_VacuumDBCounts stats;
+} PgStatShared_VacuumDB;
+
+typedef struct PgStatShared_VacuumRelation
+{
+ PgStatShared_Common header;
+ PgStat_VacuumRelationCounts stats;
+} PgStatShared_VacuumRelation;
+
typedef struct PgStatShared_Function
{
PgStatShared_Common header;
@@ -678,6 +690,9 @@ extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind,
extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid);
extern void pgstat_snapshot_fixed(PgStat_Kind kind);
+bool pgstat_vacuum_db_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern bool pgstat_vacuum_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+
/*
* Functions in pgstat_archiver.c
diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h
index eb5f0b3ae6d..52e884fbf8b 100644
--- a/src/include/utils/pgstat_kind.h
+++ b/src/include/utils/pgstat_kind.h
@@ -38,9 +38,11 @@
#define PGSTAT_KIND_IO 10
#define PGSTAT_KIND_SLRU 11
#define PGSTAT_KIND_WAL 12
+#define PGSTAT_KIND_VACUUM_DB 13
+#define PGSTAT_KIND_VACUUM_RELATION 14
#define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
-#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
+#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_VACUUM_RELATION
#define PGSTAT_KIND_BUILTIN_SIZE (PGSTAT_KIND_BUILTIN_MAX + 1)
/* Custom stats kinds */
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index bd3cb544e30..e2fd541fd89 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -28,6 +28,7 @@ $node->init;
# Configure the server logging level for the test
$node->append_conf('postgresql.conf', q{
log_min_messages = notice
+ track_vacuum_statistics = on
});
my $stderr;
@@ -64,8 +65,9 @@ $node->safe_psql($dbname, q{
$node->safe_psql(
$dbname,
- "CREATE TABLE vestat (x int PRIMARY KEY)
+ "CREATE TABLE vestat (x int)
WITH (autovacuum_enabled = off, fillfactor = 10);
+ create index vestat_pkey on vestat (x);
INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
ANALYZE vestat;"
);
@@ -115,7 +117,7 @@ sub wait_for_vacuum_stats {
AND
(SELECT (tuples_deleted > $idx_tuples_deleted AND wal_records > $idx_wal_records)
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey');"
+ WHERE indexrelname = 'vestat_pkey');"
);
return 1 if ($result_query eq 't');
@@ -183,7 +185,7 @@ sub fetch_vacuum_stats {
$dbname,
"SELECT tuples_deleted, pages_deleted, wal_records, wal_bytes, wal_fpi
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
$index_base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
@@ -321,7 +323,7 @@ sub fetch_error_base_idx_vacuum_statistics {
$dbname,
"SELECT tuples_deleted, pages_deleted
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
$base_statistics =~ s/\s*\|\s*/ /g; # transform " | " in space
my ($cur_tuples_deleted, $cur_pages_deleted) = split /\s+/, $base_statistics;
@@ -343,7 +345,7 @@ sub fetch_error_wal_idx_vacuum_statistics {
$dbname,
"SELECT wal_records, wal_bytes, wal_fpi
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
$wal_raw =~ s/\s*\|\s*/ /g; # transform " | " in space
@@ -707,7 +709,7 @@ $base_stats = $node->safe_psql(
'postgres',
"SELECT count(*) = 0
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
ok($base_stats eq 't', 'check the printing index vacuum extended statistics from another database are not available');
@@ -742,6 +744,9 @@ $reloid = $node->safe_psql(
}
);
+# Run VACUUM on shared table to ensure stats entry is created
+$node->safe_psql($dbname, "VACUUM pg_shdepend;");
+
# Check if we can get vacuum statistics for cluster relations (dbid = 0)
$base_stats = $node->safe_psql(
$dbname,
@@ -760,6 +765,10 @@ my $indoid = $node->safe_psql(
}
);
+# Run VACUUM on shared index to ensure stats entry is created
+# Note: VACUUM on the table will also vacuum its indexes
+$node->safe_psql($dbname, "VACUUM pg_shdepend;");
+
$base_stats = $node->safe_psql(
$dbname,
qq{
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
index 7528f20098b..2a1c506a22f 100644
--- a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -37,6 +37,7 @@ $node->append_conf('postgresql.conf', q{
vacuum_max_eager_freeze_failure_rate = 1.0
vacuum_failsafe_age = 0
vacuum_multixact_failsafe_age = 0
+ track_vacuum_statistics = on
});
$node->start();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b627c85e332..4e8b8b8a2b1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,77 +2330,81 @@ pg_stat_user_tables| SELECT relid,
rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
-pg_stat_vacuum_database| SELECT db.oid AS dboid,
- db.datname AS dbname,
- stats.db_blks_read,
- stats.db_blks_hit,
- stats.total_blks_dirtied,
- stats.total_blks_written,
- stats.wal_records,
- stats.wal_fpi,
- stats.wal_bytes,
- stats.blk_read_time,
- stats.blk_write_time,
- stats.delay_time,
- stats.total_time,
- stats.wraparound_failsafe,
- stats.errors
- FROM pg_database db,
- LATERAL pg_stat_get_vacuum_database(db.oid) stats(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
-pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
- ns.nspname AS schemaname,
- rel.relname,
- stats.total_blks_read,
- stats.total_blks_hit,
- stats.total_blks_dirtied,
- stats.total_blks_written,
- stats.rel_blks_read,
- stats.rel_blks_hit,
- stats.pages_deleted,
- stats.tuples_deleted,
- stats.wal_records,
- stats.wal_fpi,
- stats.wal_bytes,
- stats.blk_read_time,
- stats.blk_write_time,
- stats.delay_time,
- stats.total_time
- FROM (pg_class rel
- JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
- LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
- WHERE (rel.relkind = 'i'::"char");
-pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
- rel.relname,
- stats.relid,
- stats.total_blks_read,
- stats.total_blks_hit,
- stats.total_blks_dirtied,
- stats.total_blks_written,
- stats.rel_blks_read,
- stats.rel_blks_hit,
- stats.pages_scanned,
- stats.pages_removed,
- stats.vm_new_frozen_pages,
- stats.vm_new_visible_pages,
- stats.vm_new_visible_frozen_pages,
- stats.missed_dead_pages,
- stats.tuples_deleted,
- stats.tuples_frozen,
- stats.recently_dead_tuples,
- stats.missed_dead_tuples,
- stats.wraparound_failsafe,
- stats.index_vacuum_count,
- stats.wal_records,
- stats.wal_fpi,
- stats.wal_bytes,
- stats.blk_read_time,
- stats.blk_write_time,
- stats.delay_time,
- stats.total_time
- FROM (pg_class rel
- JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
- LATERAL pg_stat_get_vacuum_tables(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
- WHERE (rel.relkind = 'r'::"char");
+pg_stat_vacuum_database| SELECT d.oid AS dboid,
+ d.datname AS dbname,
+ s.db_blks_read,
+ s.db_blks_hit,
+ s.total_blks_dirtied,
+ s.total_blks_written,
+ s.wal_records,
+ s.wal_fpi,
+ s.wal_bytes,
+ s.blk_read_time,
+ s.blk_write_time,
+ s.delay_time,
+ s.total_time,
+ s.wraparound_failsafe,
+ s.errors
+ FROM pg_database d,
+ LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
+pg_stat_vacuum_indexes| SELECT c.oid AS relid,
+ i.oid AS indexrelid,
+ n.nspname AS schemaname,
+ c.relname,
+ i.relname AS indexrelname,
+ s.total_blks_read,
+ s.total_blks_hit,
+ s.total_blks_dirtied,
+ s.total_blks_written,
+ s.rel_blks_read,
+ s.rel_blks_hit,
+ s.pages_deleted,
+ s.tuples_deleted,
+ s.wal_records,
+ s.wal_fpi,
+ s.wal_bytes,
+ s.blk_read_time,
+ s.blk_write_time,
+ s.delay_time,
+ s.total_time
+ FROM (((pg_class c
+ JOIN pg_index x ON ((c.oid = x.indrelid)))
+ JOIN pg_class i ON ((i.oid = x.indexrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_stat_get_vacuum_indexes(i.oid) s(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
+pg_stat_vacuum_tables| SELECT n.nspname AS schemaname,
+ c.relname,
+ s.relid,
+ s.total_blks_read,
+ s.total_blks_hit,
+ s.total_blks_dirtied,
+ s.total_blks_written,
+ s.rel_blks_read,
+ s.rel_blks_hit,
+ s.pages_scanned,
+ s.pages_removed,
+ s.vm_new_frozen_pages,
+ s.vm_new_visible_pages,
+ s.vm_new_visible_frozen_pages,
+ s.missed_dead_pages,
+ s.tuples_deleted,
+ s.tuples_frozen,
+ s.recently_dead_tuples,
+ s.missed_dead_tuples,
+ s.wraparound_failsafe,
+ s.index_vacuum_count,
+ s.wal_records,
+ s.wal_fpi,
+ s.wal_bytes,
+ s.blk_read_time,
+ s.blk_write_time,
+ s.delay_time,
+ s.total_time
+ FROM (pg_class c
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
pg_stat_wal| SELECT wal_records,
wal_fpi,
wal_bytes,
--
2.39.5 (Apple Git-154)
From cfbf50c85ae9bfdfb9167dab446bf3256e33ddb1 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Thu, 19 Dec 2024 12:57:49 +0300
Subject: [PATCH 5/5] Add documentation about the system views that are used in
the machinery of vacuum statistics.
---
doc/src/sgml/system-views.sgml | 755 +++++++++++++++++++++++++++++++++
1 file changed, 755 insertions(+)
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 162c76b729a..50578cae90d 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -5654,4 +5654,759 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</table>
</sect1>
+<sect1 id="view-pg-stat-vacuum-database">
+ <title><structname>pg_stat_vacuum_database</structname></title>
+
+ <indexterm zone="view-pg-stat-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>wraparound_failsafe_count</structfield> <type>int4</type>
+ </para>
+ <para>
+ Number of times the vacuum was run to prevent a wraparound problem.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>errors</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-stat-vacuum-indexes">
+ <title><structname>pg_stat_vacuum_indexes</structname></title>
+
+ <indexterm zone="view-pg-stat-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>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
+ <sect1 id="view-pg-stat-vacuum-tables">
+ <title><structname>pg_stat_vacuum_tables</structname></title>
+
+ <indexterm zone="view-pg-stat-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 blocks written directly by vacuum or auto vacuum.
+ Blocks that are dirtied by a vacuum process can be written out by another process.
+ </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>vm_new_frozen_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of the number of pages newly set all-frozen by vacuum
+ in the visibility map.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>vm_new_visible_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of the number of pages newly set all-visible by vacuum
+ in the visibility map.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>vm_new_visible_frozen_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of the number of pages newly set all-visible and all-frozen
+ by vacuum 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>recently_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>missed_dead_tuples</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of fully DEAD (not just RECENTLY_DEAD) tuples that could not be
+ pruned due to failure to acquire a cleanup lock on a heap page.
+ </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>wraparound_failsafe_count</structfield> <type>int4</type>
+ </para>
+ <para>
+ Number of times the vacuum was run to prevent a wraparound problem.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>missed_dead_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of pages that had at least one missed_dead_tuples.
+ </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>
+
+ </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.39.5 (Apple Git-154)
Attachments:
[text/plain] v26-0001-Machinery-for-grabbing-an-extended-vacuum-statistics.patch (79.0K, 3-v26-0001-Machinery-for-grabbing-an-extended-vacuum-statistics.patch)
download | inline diff:
From f96d5079774fe129fff32761bba4ab9089e491bd Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Tue, 9 Dec 2025 09:56:34 +0300
Subject: [PATCH 1/5] Machinery for grabbing an extended vacuum statistics on
table 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).
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 is a 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 is a number of pages that are marked as all-visible in vm during
vacuum.
wraparound_failsafe_count is a number of times when the vacuum starts urgent cleanup
to prevent wraparound problem which is critical for the database.
Authors: Alena Rybakina <[email protected]>,
Andrei Lepikhov <[email protected]>,
Andrei Zubkov <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>, Masahiko Sawada <[email protected]>,
Ilia Evdokimov <[email protected]>, jian he <[email protected]>,
Kirill Reshke <[email protected]>, Alexander Korotkov <[email protected]>,
Jim Nasby <[email protected]>, Sami Imseih <[email protected]>,
Karina Litskevich <[email protected]>
---
src/backend/access/heap/vacuumlazy.c | 145 ++++-
src/backend/access/heap/visibilitymap.c | 10 +
src/backend/catalog/system_views.sql | 52 +-
src/backend/commands/vacuum.c | 4 +
src/backend/commands/vacuumparallel.c | 1 +
src/backend/utils/activity/pgstat_relation.c | 46 +-
src/backend/utils/adt/pgstatfuncs.c | 86 +++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_proc.dat | 18 +
src/include/commands/vacuum.h | 1 +
src/include/pgstat.h | 92 ++-
.../vacuum-extending-in-repetable-read.out | 53 ++
src/test/isolation/isolation_schedule | 1 +
.../vacuum-extending-in-repetable-read.spec | 53 ++
.../t/050_vacuum_extending_basic_test.pl | 571 ++++++++++++++++++
.../t/051_vacuum_extending_freeze_test.pl | 395 ++++++++++++
src/test/regress/expected/rules.out | 44 +-
src/test/regress/parallel_schedule | 2 +-
18 files changed, 1565 insertions(+), 10 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/recovery/t/050_vacuum_extending_basic_test.pl
create mode 100644 src/test/recovery/t/051_vacuum_extending_freeze_test.pl
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 30778a15639..66e09d0a0cf 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -289,6 +289,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 */
@@ -407,6 +408,10 @@ typedef struct LVRelState
* been permanently disabled.
*/
BlockNumber eager_scan_remaining_fails;
+
+ int32 wraparound_failsafe_count; /* number of emergency vacuums to
+ * prevent anti-wraparound
+ * shutdown */
} LVRelState;
@@ -418,6 +423,18 @@ typedef struct LVSavedErrInfo
VacErrPhase phase;
} LVSavedErrInfo;
+/*
+ * Counters and usage data for extended stats tracking.
+ */
+typedef struct LVExtStatCounters
+{
+ TimestampTz starttime;
+ 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);
@@ -487,6 +504,102 @@ 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;
+
+ memset(counters, 0, sizeof(LVExtStatCounters));
+
+ starttime = GetCurrentTimestamp();
+
+ counters->starttime = 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 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;
+
+ /* 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->starttime, 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;
+
+ 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;
+}
/*
@@ -645,6 +758,13 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
BufferUsage startbufferusage = pgBufferUsage;
ErrorContextCallback errcallback;
char **indnames = NULL;
+ LVExtStatCounters extVacCounters;
+ ExtVacReport extVacReport;
+ ExtVacReport allzero;
+
+ /* Initialize vacuum statistics */
+ memset(&allzero, 0, sizeof(ExtVacReport));
+ extVacReport = allzero;
verbose = (params.options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -673,6 +793,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
pgstat_progress_update_param(PROGRESS_VACUUM_STARTED_BY,
PROGRESS_VACUUM_STARTED_BY_MANUAL);
+ 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
@@ -689,6 +811,7 @@ heap_vacuum_rel(Relation rel, const 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;
@@ -797,6 +920,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
vacrel->aggressive = vacuum_get_cutoffs(rel, params, &vacrel->cutoffs);
vacrel->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel);
vacrel->vistest = GlobalVisTestFor(rel);
+ vacrel->wraparound_failsafe_count = 0;
/* Initialize state used to track oldest extant XID/MXID */
vacrel->NewRelfrozenXid = vacrel->cutoffs.OldestXmin;
@@ -951,6 +1075,23 @@ heap_vacuum_rel(Relation rel, const 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.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
+ extVacReport.vm_new_visible_pages = vacrel->vm_new_visible_pages;
+ extVacReport.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
+ extVacReport.tuples_deleted = vacrel->tuples_deleted;
+ extVacReport.tuples_frozen = vacrel->tuples_frozen;
+ extVacReport.recently_dead_tuples = vacrel->recently_dead_tuples;
+ extVacReport.missed_dead_tuples = vacrel->missed_dead_tuples;
+ extVacReport.missed_dead_pages = vacrel->missed_dead_pages;
+ extVacReport.index_vacuum_count = vacrel->num_index_scans;
+ extVacReport.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+
/*
* Report results to the cumulative stats system, too.
*
@@ -965,7 +1106,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
Max(vacrel->new_live_tuples, 0),
vacrel->recently_dead_tuples +
vacrel->missed_dead_tuples,
- starttime);
+ starttime,
+ &extVacReport);
pgstat_progress_end_command();
if (instrument)
@@ -3019,6 +3161,7 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
int64 progress_val[3] = {0, 0, PROGRESS_VACUUM_MODE_FAILSAFE};
VacuumFailsafeActive = true;
+ vacrel->wraparound_failsafe_count++;
/*
* Abandon use of a buffer access strategy to allow use of all of
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index d14588e92ae..3030242d98e 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -92,6 +92,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"
@@ -161,6 +162,15 @@ visibilitymap_clear(Relation rel, BlockNumber heapBlk, Buffer vmbuf, uint8 flags
if (map[mapByte] & mask)
{
+ /*
+ * As part of vacuum stats, track how often all-visible or all-frozen
+ * bits are cleared.
+ */
+ 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 0a0f95f6bb9..ffb407d414f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -727,7 +727,9 @@ CREATE VIEW pg_stat_all_tables AS
pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time,
pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time,
pg_stat_get_total_autoanalyze_time(C.oid) AS total_autoanalyze_time,
- pg_stat_get_stat_reset_time(C.oid) AS stats_reset
+ pg_stat_get_stat_reset_time(C.oid) AS stats_reset,
+ pg_stat_get_rev_all_frozen_pages(C.oid) AS rev_all_frozen_pages,
+ pg_stat_get_rev_all_visible_pages(C.oid) AS rev_all_visible_pages
FROM pg_class C LEFT JOIN
pg_index I ON C.oid = I.indrelid
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -1452,3 +1454,51 @@ REVOKE ALL ON pg_aios FROM PUBLIC;
GRANT SELECT ON pg_aios TO pg_read_all_stats;
REVOKE EXECUTE ON FUNCTION pg_get_aios() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION pg_get_aios() TO pg_read_all_stats;
+--
+-- 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
+ ns.nspname AS schemaname,
+ rel.relname AS relname,
+ stats.relid as relid,
+
+ stats.total_blks_read AS total_blks_read,
+ stats.total_blks_hit AS total_blks_hit,
+ stats.total_blks_dirtied AS total_blks_dirtied,
+ stats.total_blks_written AS total_blks_written,
+
+ stats.rel_blks_read AS rel_blks_read,
+ stats.rel_blks_hit AS rel_blks_hit,
+
+ stats.pages_scanned AS pages_scanned,
+ stats.pages_removed AS pages_removed,
+ stats.vm_new_frozen_pages AS vm_new_frozen_pages,
+ stats.vm_new_visible_pages AS vm_new_visible_pages,
+ stats.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
+ stats.missed_dead_pages AS missed_dead_pages,
+ stats.tuples_deleted AS tuples_deleted,
+ stats.tuples_frozen AS tuples_frozen,
+ stats.recently_dead_tuples AS recently_dead_tuples,
+ stats.missed_dead_tuples AS missed_dead_tuples,
+
+ stats.wraparound_failsafe AS wraparound_failsafe,
+ stats.index_vacuum_count AS index_vacuum_count,
+ stats.wal_records AS wal_records,
+ stats.wal_fpi AS wal_fpi,
+ stats.wal_bytes AS wal_bytes,
+
+ stats.blk_read_time AS blk_read_time,
+ stats.blk_write_time AS blk_write_time,
+
+ stats.delay_time AS delay_time,
+ stats.total_time AS total_time
+
+FROM pg_class rel
+ JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
+ LATERAL pg_stat_get_vacuum_tables(rel.oid) stats
+WHERE rel.relkind = 'r';
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0528d1b6ecb..dd519447387 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -117,6 +117,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);
@@ -2536,6 +2539,7 @@ vacuum_delay_point(bool is_analyze)
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 8a37c08871a..114cd7c31d3 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1054,6 +1054,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_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 55a10c299db..361713479e8 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -47,6 +47,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);
/*
@@ -208,7 +210,7 @@ pgstat_drop_relation(Relation rel)
*/
void
pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
- PgStat_Counter deadtuples, TimestampTz starttime)
+ PgStat_Counter deadtuples, TimestampTz starttime, ExtVacReport * params)
{
PgStat_EntryRef *entry_ref;
PgStatShared_Relation *shtabentry;
@@ -234,6 +236,8 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
tabentry->live_tuples = livetuples;
tabentry->dead_tuples = deadtuples;
+ pgstat_accumulate_extvac_stats(&tabentry->vacuum_ext, params, true);
+
/*
* It is quite possible that a non-aggressive VACUUM ended up skipping
* various pages, however, we'll zero the insert counter here regardless.
@@ -880,6 +884,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 */
@@ -1009,3 +1016,40 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
trans->tuples_deleted = trans->deleted_pre_truncdrop;
}
}
+
+static void
+pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
+ bool accumulate_reltype_specific_info)
+{
+ dst->total_blks_read += src->total_blks_read;
+ dst->total_blks_hit += src->total_blks_hit;
+ dst->total_blks_dirtied += src->total_blks_dirtied;
+ dst->total_blks_written += src->total_blks_written;
+ dst->wal_bytes += src->wal_bytes;
+ dst->wal_fpi += src->wal_fpi;
+ dst->wal_records += src->wal_records;
+ dst->blk_read_time += src->blk_read_time;
+ dst->blk_write_time += src->blk_write_time;
+ dst->delay_time += src->delay_time;
+ dst->total_time += src->total_time;
+
+ 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->vm_new_frozen_pages += src->vm_new_frozen_pages;
+ dst->vm_new_visible_pages += src->vm_new_visible_pages;
+ dst->vm_new_visible_frozen_pages += src->vm_new_visible_frozen_pages;
+ dst->tuples_deleted += src->tuples_deleted;
+ dst->tuples_frozen += src->tuples_frozen;
+ dst->recently_dead_tuples += src->recently_dead_tuples;
+ dst->index_vacuum_count += src->index_vacuum_count;
+ dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
+ dst->missed_dead_pages += src->missed_dead_pages;
+ dst->missed_dead_tuples += src->missed_dead_tuples;
+
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ef6fffe60b9..d7dfda0c1a7 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -106,6 +106,12 @@ PG_STAT_GET_RELENTRY_INT64(tuples_updated)
/* pg_stat_get_vacuum_count */
PG_STAT_GET_RELENTRY_INT64(vacuum_count)
+/* pg_stat_get_rev_frozen_pages */
+PG_STAT_GET_RELENTRY_INT64(rev_all_frozen_pages)
+
+/* pg_stat_get_rev_all_visible_pages */
+PG_STAT_GET_RELENTRY_INT64(rev_all_visible_pages)
+
#define PG_STAT_GET_RELENTRY_FLOAT8(stat) \
Datum \
CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS) \
@@ -2307,3 +2313,83 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objid));
}
+
+
+/*
+ * Get the vacuum statistics for the heap tables.
+ */
+Datum
+pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 26
+
+ Oid relid = PG_GETARG_OID(0);
+ PgStat_StatTabEntry *tabentry;
+ ExtVacReport *extvacuum;
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
+ char buf[256];
+ int i = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tabentry = pgstat_fetch_stat_tabentry(relid);
+
+ if (!tabentry)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
+ else
+ {
+ extvacuum = &(tabentry->vacuum_ext);
+ }
+
+ i = 0;
+
+ values[i++] = ObjectIdGetDatum(relid);
+
+ values[i++] = Int64GetDatum(extvacuum->total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+
+ values[i++] = Int64GetDatum(extvacuum->blks_fetched -
+ extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->blks_hit);
+
+ values[i++] = Int64GetDatum(extvacuum->pages_scanned);
+ values[i++] = Int64GetDatum(extvacuum->pages_removed);
+ values[i++] = Int64GetDatum(extvacuum->vm_new_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->vm_new_visible_pages);
+ values[i++] = Int64GetDatum(extvacuum->vm_new_visible_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->missed_dead_pages);
+ values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+ values[i++] = Int64GetDatum(extvacuum->tuples_frozen);
+ values[i++] = Int64GetDatum(extvacuum->recently_dead_tuples);
+ values[i++] = Int64GetDatum(extvacuum->missed_dead_tuples);
+ values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+ values[i++] = Int64GetDatum(extvacuum->index_vacuum_count);
+
+ values[i++] = Int64GetDatum(extvacuum->wal_records);
+ values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+ /* Convert to numeric, like pg_stat_statements */
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ values[i++] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ values[i++] = Float8GetDatum(extvacuum->blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->delay_time);
+ values[i++] = Float8GetDatum(extvacuum->total_time);
+
+ Assert(i == PG_STAT_GET_VACUUM_TABLES_STATS_COLS);
+
+ /* Returns the record as Datum */
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..867638fe74b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -669,6 +669,7 @@
#track_wal_io_timing = off
#track_functions = none # none, pl, all
#stats_fetch_consistency = cache # cache, none, snapshot
+#track_vacuum_statistics = off
# - Monitoring -
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fd9448ec7b9..915a5a7822f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12612,4 +12612,22 @@
proargnames => '{pid,io_id,io_generation,state,operation,off,length,target,handle_data_len,raw_result,result,target_desc,f_sync,f_localmem,f_buffered}',
prosrc => 'pg_get_aios' },
+{ oid => '8001',
+ descr => 'pg_stat_get_vacuum_tables returns vacuum stats values for table',
+ proname => 'pg_stat_get_vacuum_tables', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
+ proretset => 't',
+ proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int4,int8,int8,int8,numeric,float8,float8,float8,float8}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,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,vm_new_frozen_pages,vm_new_visible_pages,vm_new_visible_frozen_pages,missed_dead_pages,tuples_deleted,tuples_frozen,recently_dead_tuples,missed_dead_tuples,wraparound_failsafe,index_vacuum_count,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time}',
+ prosrc => 'pg_stat_get_vacuum_tables' },
+
+ { oid => '8002', descr => 'statistics: number of times the all-visible pages in the visibility map was removed for pages of table',
+ proname => 'pg_stat_get_rev_all_visible_pages', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_rev_all_visible_pages' },
+ { oid => '8003', descr => 'statistics: number of times the all-frozen pages in the visibility map was removed for pages of table',
+ proname => 'pg_stat_get_rev_all_frozen_pages', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_rev_all_frozen_pages' },
]
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 1f3290c7fbf..6b997bc7fb1 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -332,6 +332,7 @@ extern PGDLLIMPORT double vacuum_max_eager_freeze_failure_rate;
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 6714363144a..46d12fa3bd0 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -114,6 +114,66 @@ typedef struct PgStat_BackendSubEntry
PgStat_Counter conflict_count[CONFLICT_NUM_TYPES];
} 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
+{
+ /*
+ * number of blocks missed, hit, dirtied and written during a vacuum of
+ * specific relation
+ */
+ int64 total_blks_read;
+ int64 total_blks_hit;
+ int64 total_blks_dirtied;
+ int64 total_blks_written;
+
+ /*
+ * blocks missed and hit for just the heap during a vacuum of specific
+ * relation
+ */
+ int64 blks_fetched;
+ int64 blks_hit;
+
+ /* Vacuum WAL usage stats */
+ int64 wal_records; /* wal usage: number of WAL records */
+ int64 wal_fpi; /* wal usage: number of WAL full page images
+ * produced */
+ uint64 wal_bytes; /* wal usage: size of WAL records produced */
+
+ /* Time stats. */
+ double blk_read_time; /* time spent reading pages, in msec */
+ double blk_write_time; /* time spent writing pages, in msec */
+ double delay_time; /* how long vacuum slept in vacuum delay
+ * point, in msec */
+ double total_time; /* total time of a vacuum operation, in msec */
+
+ int64 pages_scanned; /* heap pages examined (not skipped by VM) */
+ int64 pages_removed; /* heap pages removed by vacuum "truncation" */
+ int64 vm_new_frozen_pages; /* pages marked in VM as frozen */
+ int64 vm_new_visible_pages; /* pages marked in VM as all-visible */
+ int64 vm_new_visible_frozen_pages; /* pages marked in VM as
+ * all-visible and frozen */
+ int64 missed_dead_tuples; /* tuples not pruned by vacuum due to
+ * failure to get a cleanup lock */
+ int64 missed_dead_pages; /* pages with missed dead tuples */
+ int64 tuples_deleted; /* tuples deleted by vacuum */
+ int64 tuples_frozen; /* tuples frozen up by vacuum */
+ int64 recently_dead_tuples; /* deleted tuples that are still
+ * visible to some transaction */
+ int64 index_vacuum_count; /* the number of index vacuumings */
+ int32 wraparound_failsafe_count; /* number of emergency vacuums to
+ * prevent anti-wraparound
+ * shutdown */
+} ExtVacReport;
+
/* ----------
* PgStat_TableCounts The actual per-table counts kept by a backend
*
@@ -156,6 +216,15 @@ 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;
/* ----------
@@ -214,7 +283,7 @@ typedef struct PgStat_TableXactStatus
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BCBB
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BCBC
typedef struct PgStat_ArchiverStats
{
@@ -378,6 +447,8 @@ typedef struct PgStat_StatDBEntry
PgStat_Counter parallel_workers_launched;
TimestampTz stat_reset_timestamp;
+
+ ExtVacReport vacuum_ext; /* extended vacuum statistics */
} PgStat_StatDBEntry;
typedef struct PgStat_StatFuncEntry
@@ -461,8 +532,12 @@ typedef struct PgStat_StatTabEntry
PgStat_Counter total_autovacuum_time;
PgStat_Counter total_analyze_time;
PgStat_Counter total_autoanalyze_time;
-
TimestampTz stat_reset_time;
+
+ PgStat_Counter rev_all_visible_pages;
+ PgStat_Counter rev_all_frozen_pages;
+
+ ExtVacReport vacuum_ext;
} PgStat_StatTabEntry;
/* ------
@@ -671,7 +746,7 @@ extern void pgstat_unlink_relation(Relation rel);
extern void pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
PgStat_Counter deadtuples,
- TimestampTz starttime);
+ TimestampTz starttime, ExtVacReport * params);
extern void pgstat_report_analyze(Relation rel,
PgStat_Counter livetuples, PgStat_Counter deadtuples,
bool resetcounter, TimestampTz starttime);
@@ -722,6 +797,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);
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..87f7e40b4a6
--- /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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
+--------------------------+--------------+--------------------+------------------+-----------------+-------------
+test_vacuum_stat_isolation| 0| 0| 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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
+--------------------------+--------------+--------------------+------------------+-----------------+-------------
+test_vacuum_stat_isolation| 0| 100| 0| 0| 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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
+--------------------------+--------------+--------------------+------------------+-----------------+-------------
+test_vacuum_stat_isolation| 100| 100| 0| 0| 101
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f2e067b1fbc..1c231418706 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -98,6 +98,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..5893d89573d
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -0,0 +1,53 @@
+# Test for checking recently_dead_tuples, tuples_deleted and frozen tuples in pg_stat_vacuum_tables.
+# recently_dead_tuples values are counted when vacuum hasn't cleared tuples because they were deleted recently.
+# recently_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;
+ SET track_vacuum_statistics TO 'on';
+}
+
+teardown
+{
+ DROP TABLE test_vacuum_stat_isolation CASCADE;
+ RESET track_io_timing;
+ RESET track_vacuum_statistics;
+}
+
+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.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, 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
\ No newline at end of file
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
new file mode 100644
index 00000000000..7e25a3fe63f
--- /dev/null
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -0,0 +1,571 @@
+# Copyright (c) 2025 PostgreSQL Global Development Group
+# Test cumulative vacuum stats system using TAP
+#
+# This test validates the accuracy and behavior of cumulative vacuum statistics
+# across tables using:
+#
+# • pg_stat_vacuum_tables
+#
+# A polling helper function repeatedly checks the stats views until expected
+# deltas appear or a configurable timeout expires. This guarantees that
+# stats-collector propagation delays do not lead to flaky test behavior.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+#------------------------------------------------------------------------------
+# Test harness setup
+#------------------------------------------------------------------------------
+
+my $node = PostgreSQL::Test::Cluster->new('stat_vacuum');
+$node->init;
+
+# Configure the server logging level for the test
+$node->append_conf('postgresql.conf', q{
+ log_min_messages = notice
+});
+
+my $stderr;
+my $base_stats;
+my $wals;
+my $ibase_stats;
+my $iwals;
+
+$node->start(
+ '>' => \$base_stats,
+ '2>' => \$stderr
+);
+
+#------------------------------------------------------------------------------
+# Database creation and initialization
+#------------------------------------------------------------------------------
+
+$node->safe_psql('postgres', q{
+ CREATE DATABASE statistic_vacuum_database_regression;
+});
+# Main test database name and number of rows to insert
+my $dbname = 'statistic_vacuum_database_regression';
+my $size_tab = 1000;
+
+# Enable required session settings and force the stats collector to flush next
+$node->safe_psql($dbname, q{
+ SET track_functions = 'all';
+ SELECT pg_stat_force_next_flush();
+});
+
+#------------------------------------------------------------------------------
+# Create test table and populate it
+#------------------------------------------------------------------------------
+
+$node->safe_psql(
+ $dbname,
+ "CREATE TABLE vestat (x int)
+ WITH (autovacuum_enabled = off, fillfactor = 10);
+ INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ ANALYZE vestat;"
+);
+
+#------------------------------------------------------------------------------
+# Timing parameters for polling loops
+#------------------------------------------------------------------------------
+
+my $timeout = 30; # overall wait timeout in seconds
+my $interval = 0.015; # poll interval in seconds (15 ms)
+my $start_time = time();
+my $updated = 0;
+
+#------------------------------------------------------------------------------
+# wait_for_vacuum_stats
+#
+# Polls pg_stat_vacuum_tables until the table-level counters exceed
+# the provided baselines, or until the configured timeout elapses.
+#
+# Expected named args (baseline values):
+# tab_tuples_deleted
+# tab_wal_records
+#
+# Returns: 1 if the condition is met before timeout, 0 otherwise.
+#------------------------------------------------------------------------------
+
+sub wait_for_vacuum_stats {
+ my (%args) = @_;
+ my $tab_tuples_deleted = $args{tab_tuples_deleted} or 0;
+ my $tab_wal_records = $args{tab_wal_records} or 0;
+
+ my $start = time();
+ while ((time() - $start) < $timeout) {
+
+ my $result_query = $node->safe_psql(
+ $dbname,
+ "VACUUM vestat;
+ SELECT tuples_deleted > $tab_tuples_deleted AND wal_records > $tab_wal_records
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ return 1 if ($result_query eq 't');
+
+ sleep($interval);
+ }
+
+ return 0;
+}
+
+#------------------------------------------------------------------------------
+# Variables to hold vacuum-stat snapshots for later comparisons
+#------------------------------------------------------------------------------
+
+my $pages_frozen = 0;
+my $tuples_deleted = 0;
+my $pages_scanned = 0;
+my $pages_removed = 0;
+my $wal_records = 0;
+my $wal_bytes = 0;
+my $wal_fpi = 0;
+
+my $pages_frozen_prev = 0;
+my $tuples_deleted_prev = 0;
+my $pages_scanned_prev = 0;
+my $pages_removed_prev = 0;
+my $wal_records_prev = 0;
+my $wal_bytes_prev = 0;
+my $wal_fpi_prev = 0;
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats
+#
+# Reads current values of relevant vacuum counters for the test table,
+# storing them in package variables for subsequent comparisons.
+#------------------------------------------------------------------------------
+
+sub fetch_vacuum_stats {
+ # fetch actual base vacuum statistics
+ my $base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT vm_new_frozen_pages, tuples_deleted, pages_scanned, pages_removed, wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ $base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
+ ($pages_frozen, $tuples_deleted, $pages_scanned, $pages_removed, $wal_records, $wal_bytes, $wal_fpi)
+ = split /\s+/, $base_statistics;
+}
+
+#------------------------------------------------------------------------------
+# save_vacuum_stats
+#
+# Save current values (previously fetched by fetch_vacuum_stats) so that we
+# later fetch new values and compare them.
+#------------------------------------------------------------------------------
+sub save_vacuum_stats {
+ $pages_frozen_prev = $pages_frozen;
+ $tuples_deleted_prev = $tuples_deleted;
+ $pages_scanned_prev = $pages_scanned;
+ $pages_removed_prev = $pages_removed;
+ $wal_records_prev = $wal_records;
+ $wal_bytes_prev = $wal_bytes;
+ $wal_fpi_prev = $wal_fpi;
+}
+
+#------------------------------------------------------------------------------
+# print_vacuum_stats_on_error
+#
+# Print values in case of an error
+#------------------------------------------------------------------------------
+sub print_vacuum_stats_on_error {
+ diag(
+ "Statistics in the failed test\n" .
+ "Table statistics:\n" .
+ " Before test:\n" .
+ " pages_frozen = $pages_frozen_prev\n" .
+ " tuples_deleted = $tuples_deleted_prev\n" .
+ " pages_scanned = $pages_scanned_prev\n" .
+ " pages_removed = $pages_removed_prev\n" .
+ " wal_records = $wal_records_prev\n" .
+ " wal_bytes = $wal_bytes_prev\n" .
+ " wal_fpi = $wal_fpi_prev\n" .
+ " After test:\n" .
+ " pages_frozen = $pages_frozen\n" .
+ " tuples_deleted = $tuples_deleted\n" .
+ " pages_scanned = $pages_scanned\n" .
+ " pages_removed = $pages_removed\n" .
+ " wal_records = $wal_records\n" .
+ " wal_bytes = $wal_bytes\n" .
+ " wal_fpi = $wal_fpi\n"
+ );
+};
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats during mismatch
+#
+# Print current values and old values of relevant vacuum counters for the test
+# table, storing them in package variables for subsequent comparisons.
+#------------------------------------------------------------------------------
+
+sub fetch_error_base_tab_vacuum_statistics {
+
+ # fetch actual base vacuum statistics
+ my $base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT vm_new_frozen_pages, tuples_deleted, pages_scanned, pages_removed
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+ $base_statistics =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_pages_frozen, $cur_tuples_deleted, $cur_pages_scanned, $cur_pages_removed) = split /\s+/, $base_statistics;
+
+ diag(
+ "BASE STATS MISMATCH FOR TABLE:\n" .
+ " Baseline:\n" .
+ " pages_frozen = $pages_frozen\n" .
+ " tuples_deleted = $tuples_deleted\n" .
+ " pages_scanned = $pages_scanned\n" .
+ " pages_removed = $pages_removed\n" .
+ " Current:\n" .
+ " pages_frozen = $cur_pages_frozen\n" .
+ " tuples_deleted = $cur_tuples_deleted\n" .
+ " pages_scanned = $cur_pages_scanned\n" .
+ " pages_removed = $cur_pages_removed\n"
+ );
+}
+
+sub fetch_error_wal_tab_vacuum_statistics {
+
+ my $wal_raw = $node->safe_psql(
+ $dbname,
+ "SELECT wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ $wal_raw =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_wal_rec, $cur_wal_bytes, $cur_wal_fpi) = split /\s+/, $wal_raw;
+
+ diag(
+ "WAL STATS MISMATCH FOR TABLE:\n" .
+ " Baseline:\n" .
+ " wal_records = $wal_records\n" .
+ " wal_bytes = $wal_bytes\n" .
+ " wal_fpi = $wal_fpi\n" .
+ " Current:\n" .
+ " wal_records = $cur_wal_rec\n" .
+ " wal_bytes = $cur_wal_bytes\n" .
+ " wal_fpi = $cur_wal_fpi\n"
+ );
+}
+
+#------------------------------------------------------------------------------
+# Test 1: Delete half the rows, run VACUUM, and wait for stats to advance
+#------------------------------------------------------------------------------
+subtest 'Test 1: Delete half the rows, run VACUUM, and wait for stats to advance' => sub
+{
+
+$node->safe_psql($dbname, "DELETE FROM vestat WHERE x % 2 = 0;");
+$node->safe_psql($dbname, "VACUUM vestat;");
+
+# Poll the stats view until expected deltas appear or timeout
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => 0,
+ tab_wal_records => 0
+);
+ok($updated, 'vacuum stats updated after vacuuming half-deleted table (tuples_deleted and wal_fpi advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds after vacuuming half-deleted table";
+
+#------------------------------------------------------------------------------
+# Check statistics after half-table delete
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted > $tuples_deleted_prev, 'table tuples_deleted has increased');
+ok($pages_scanned > $pages_scanned_prev, 'table pages_scanned has increased');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 2: Delete all rows, run VACUUM, and wait for stats to advance
+#------------------------------------------------------------------------------
+subtest 'Test 2: Delete all rows, run VACUUM, and wait for stats to advance' => sub
+{
+
+$node->safe_psql($dbname, "DELETE FROM vestat;");
+$node->safe_psql($dbname, "VACUUM vestat;");
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => $tuples_deleted_prev,
+ tab_wal_records => $wal_records_prev,
+);
+
+ok($updated, 'vacuum stats updated after vacuuming all-deleted table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds after vacuuming all-deleted table";
+
+#------------------------------------------------------------------------------
+# Check statistics after full delete
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted > $tuples_deleted_prev, 'table tuples_deleted has increased');
+ok($pages_scanned > $pages_scanned_prev, 'table pages_scanned has increased');
+ok($pages_removed > $pages_removed_prev, 'table pages_removed has increased');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 3: Test VACUUM FULL — it should not report to the stats collector
+#------------------------------------------------------------------------------
+subtest 'Test 3: Test VACUUM FULL — it should not report to the stats collector' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ CHECKPOINT;
+ DELETE FROM vestat;
+ VACUUM FULL vestat;"
+);
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted == $tuples_deleted_prev, 'table tuples_deleted stay the same');
+ok($pages_scanned == $pages_scanned_prev, 'table pages_scanned stay the same');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records == $wal_records_prev, 'table wal_records stay the same');
+ok($wal_bytes == $wal_bytes_prev, 'table wal_bytes stay the same');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 4: Update table, checkpoint, and VACUUM to provoke WAL/FPI accounting
+#------------------------------------------------------------------------------
+subtest 'Test 4: Update table, checkpoint, and VACUUM to provoke WAL/FPI accounting' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ CHECKPOINT;
+ UPDATE vestat SET x = x + 1000;
+ VACUUM vestat;"
+);
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => $tuples_deleted,
+ tab_wal_records => $wal_records,
+);
+
+ok($updated, 'vacuum stats updated after updating tuples in the table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Verify statistics after updating tuples and vacuuming
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted > $tuples_deleted_prev, 'table tuples_deleted has increased');
+ok($pages_scanned > $pages_scanned_prev, 'table pages_scanned has increased');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 5: Update table, trancate and vacuuming
+#------------------------------------------------------------------------------
+subtest 'Test 5: Update table, trancate and vacuuming' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ UPDATE vestat SET x = x + 1000;"
+);
+$node->safe_psql($dbname, "TRUNCATE vestat;");
+$node->safe_psql($dbname, "CHECKPOINT;");
+$node->safe_psql($dbname, "VACUUM vestat;");
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => 0,
+ tab_wal_records => $wal_records_prev
+);
+
+ok($updated, 'vacuum stats updated after updating tuples and trancation in the table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Verify statistics after updating full table, vacuum and trancation
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted == $tuples_deleted_prev, 'table tuples_deleted stay the same');
+ok($pages_scanned == $pages_scanned_prev, 'table pages_scanned stay the same');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 6: Delete all tuples from table, trancate, and vacuuming
+#------------------------------------------------------------------------------
+subtest 'Test 6: Delete all tuples from table, trancate, and vacuuming' => sub
+{
+
+$node->safe_psql(
+ $dbname,
+ "INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
+ DELETE FROM vestat;
+ TRUNCATE vestat;
+ CHECKPOINT;
+ VACUUM vestat;"
+);
+
+$updated = wait_for_vacuum_stats(
+ tab_tuples_deleted => 0,
+ tab_wal_records => $wal_records
+);
+
+ok($updated, 'vacuum stats updated after deleting all tuples and trancation in the table (tuples_deleted and wal_records advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Verify statistics after table vacuum and trancation
+#------------------------------------------------------------------------------
+
+# Get current statistics
+fetch_vacuum_stats();
+
+ok($pages_frozen == $pages_frozen_prev, 'table pages_frozen stay the same');
+ok($tuples_deleted == $tuples_deleted_prev, 'table tuples_deleted stay the same');
+ok($pages_scanned == $pages_scanned_prev, 'table pages_scanned stay the same');
+ok($pages_removed == $pages_removed_prev, 'table pages_removed stay the same');
+ok($wal_records > $wal_records_prev, 'table wal_records has increased');
+ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
+ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+
+} or print_vacuum_stats_on_error(); # End of subtest
+
+# Save statistics for the next test
+save_vacuum_stats();
+
+#-------------------------------------------------------------------------------------------------------
+# Test 8: Check if we return single vacuum statistics for particular relation from the current database
+#-------------------------------------------------------------------------------------------------------
+
+my $dboid = $node->safe_psql(
+ $dbname,
+ "SELECT oid FROM pg_database WHERE datname = current_database();"
+);
+
+my $reloid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'vestat';
+ }
+);
+
+# Check if we can get vacuum statistics of particular heap elation in the current database
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 1 FROM pg_stat_get_vacuum_tables($reloid);"
+);
+ok($base_stats eq 't', 'heap vacuum stats return from the current relation and database as expected');
+
+#------------------------------------------------------------------------------
+# Test 9: Check relation-level vacuum statistics from another database
+#------------------------------------------------------------------------------
+
+$base_stats = $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) = 0
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+);
+ok($base_stats eq 't', 'check the printing heap vacuum extended statistics from another database are not available');
+
+$reloid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'pg_shdepend';
+ }
+);
+
+# Check if we can get vacuum statistics for cluster relations (dbid = 0)
+$base_stats = $node->safe_psql(
+ $dbname,
+ qq{
+ SELECT count(*) = 1
+ FROM pg_stat_get_vacuum_tables($reloid);
+ }
+);
+
+is($base_stats, 't', 'vacuum stats for common heap objects available');
+
+#------------------------------------------------------------------------------
+# Test 11: Cleanup checks: ensure functions return empty sets for OID = 0
+#------------------------------------------------------------------------------
+
+$node->safe_psql($dbname, q{
+ DROP TABLE vestat CASCADE;
+ VACUUM;
+});
+
+# Check that we don't print vacuum statistics for deleted objects
+$base_stats = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT COUNT(*) = 0
+ FROM pg_stat_vacuum_tables WHERE relid = 0;
+ }
+);
+ok($base_stats eq 't', 'pg_stat_vacuum_tables correctly returns no rows for OID = 0');
+
+$node->safe_psql('postgres',
+ "DROP DATABASE $dbname;"
+);
+
+$node->stop;
+
+done_testing();
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
new file mode 100644
index 00000000000..a9b5d6cb739
--- /dev/null
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -0,0 +1,395 @@
+# Copyright (c) 2025 PostgreSQL Global Development Group
+#
+# Test cumulative vacuum stats system using TAP
+#
+# In short, this test validates the correctness and stability of cumulative
+# vacuum statistics accounting around freezing, visibility, and revision
+# tracking across multiple VACUUMs and backend operations.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+#------------------------------------------------------------------------------
+# Test cluster setup
+#------------------------------------------------------------------------------
+
+my $node = PostgreSQL::Test::Cluster->new('ext_stat_vacuum');
+$node->init;
+
+# Configure the server for aggressive freezing behavior used by the test
+# These settings ensure that VACUUM always freezes pages aggressively:
+# - vacuum_freeze_min_age = 0: freeze tuples as soon as possible (no age requirement)
+# - vacuum_freeze_table_age = 0: always perform aggressive scan (scan all pages)
+# - vacuum_multixact_freeze_min_age = 0: freeze multixacts as soon as possible
+# - vacuum_multixact_freeze_table_age = 0: always perform aggressive scan for multixacts
+# - vacuum_max_eager_freeze_failure_rate = 1.0: enable aggressive eager scanning (100% of pages)
+# - vacuum_failsafe_age = 0: disable failsafe (for testing)
+# - vacuum_multixact_failsafe_age = 0: disable multixact failsafe (for testing)
+$node->append_conf('postgresql.conf', q{
+ log_min_messages = notice
+ vacuum_freeze_min_age = 0
+ vacuum_freeze_table_age = 0
+ vacuum_multixact_freeze_min_age = 0
+ vacuum_multixact_freeze_table_age = 0
+ vacuum_max_eager_freeze_failure_rate = 1.0
+ vacuum_failsafe_age = 0
+ vacuum_multixact_failsafe_age = 0
+});
+
+$node->start();
+
+#------------------------------------------------------------------------------
+# Database creation and initialization
+#------------------------------------------------------------------------------
+
+$node->safe_psql('postgres', q{
+ CREATE DATABASE statistic_vacuum_database_regression;
+});
+
+# Main test database name
+my $dbname = 'statistic_vacuum_database_regression';
+
+# Enable necessary settings and force the stats collector to flush next
+$node->safe_psql($dbname, q{
+ SET track_functions = 'all';
+ SELECT pg_stat_force_next_flush();
+});
+
+#------------------------------------------------------------------------------
+# Timing parameters for polling loops
+#------------------------------------------------------------------------------
+
+my $timeout = 30; # overall wait timeout in seconds
+my $interval = 0.015; # poll interval in seconds (15 ms)
+my $start_time = time();
+my $updated = 0;
+
+# wait_for_vacuum_stats
+#
+# Polls pg_stat_vacuum_tables until the named columns exceed the provided
+# baseline values or until timeout. Callers should pass:
+#
+# tab_frozen_column => 'vm_new_frozen_pages' # column name (string) or 'rev_all_frozen_pages'
+# tab_visible_column => 'vm_new_visible_pages' # column name (string) or 'rev_all_visible_pages'
+# tab_all_frozen_pages_count => 0 # baseline numeric
+# tab_all_visible_pages_count => 0 # baseline numeric
+# run_vacuum => 0 or 1 # if true, run vacuum_sql before polling
+#
+# Returns: 1 if the condition is met before timeout, 0 otherwise.
+sub wait_for_vacuum_stats {
+ my (%args) = @_;
+
+ my $tab_frozen_column = $args{tab_frozen_column};
+ my $tab_visible_column = $args{tab_visible_column};
+ my $tab_all_frozen_pages_count = $args{tab_all_frozen_pages_count};
+ my $tab_all_visible_pages_count = $args{tab_all_visible_pages_count};
+ my $run_vacuum = $args{run_vacuum} ? 1 : 0;
+ my $result_query;
+
+ my $start = time();
+ my $sql;
+
+ while ((time() - $start) < $timeout) {
+
+ if ($run_vacuum) {
+ $node->safe_psql($dbname, 'VACUUM (FREEZE, VERBOSE) vestat');
+ $sql = "
+ SELECT ($tab_frozen_column > $tab_all_frozen_pages_count AND
+ $tab_visible_column > $tab_all_visible_pages_count)
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat'";
+ }
+ else {
+ $sql = "
+ SELECT (pg_stat_get_rev_all_frozen_pages(c.oid) > $tab_all_frozen_pages_count AND
+ pg_stat_get_rev_all_visible_pages(c.oid) > $tab_all_visible_pages_count)
+ FROM pg_class c
+ WHERE relname = 'vestat'";
+ }
+
+ $result_query = $node->safe_psql($dbname, $sql);
+
+ return 1 if (defined $result_query && $result_query eq 't');
+
+ # sub-second sleep
+ sleep($interval);
+ }
+
+ return 0;
+}
+
+#------------------------------------------------------------------------------
+# Variables to hold vacuum statistics snapshots for comparisons
+#------------------------------------------------------------------------------
+
+my $vm_new_frozen_pages;
+my $vm_new_visible_pages;
+
+my $rev_all_frozen_pages;
+my $rev_all_visible_pages;
+
+my $res;
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats
+#
+# Loads current values of the relevant vacuum counters for the test table
+# into the package-level variables above so tests can compare later.
+#------------------------------------------------------------------------------
+
+sub fetch_vacuum_stats {
+ # fetch actual base vacuum statistics
+ $vm_new_frozen_pages = $node->safe_psql(
+ $dbname,
+ "SELECT vt.vm_new_frozen_pages
+ FROM pg_stat_vacuum_tables vt
+ WHERE vt.relname = 'vestat';"
+ );
+
+ $vm_new_visible_pages = $node->safe_psql(
+ $dbname,
+ "SELECT vt.vm_new_visible_pages
+ FROM pg_stat_vacuum_tables vt
+ WHERE vt.relname = 'vestat';"
+ );
+
+ $rev_all_frozen_pages = $node->safe_psql(
+ $dbname,
+ "SELECT pg_stat_get_rev_all_frozen_pages(c.oid)
+ FROM pg_class c
+ WHERE c.relname = 'vestat';"
+ );
+
+ $rev_all_visible_pages = $node->safe_psql(
+ $dbname,
+ "SELECT pg_stat_get_rev_all_visible_pages(c.oid)
+ FROM pg_class c
+ WHERE c.relname = 'vestat';"
+ );
+}
+
+#------------------------------------------------------------------------------
+# fetch_vacuum_stats during mismatch
+#
+# Print current values and old values of relevant vacuum counters for the test
+# table, storing them in package variables for subsequent comparisons.
+#------------------------------------------------------------------------------
+
+sub fetch_error_tab_vacuum_statistics {
+ my (%args) = @_;
+
+ # Validate presence of required args (allow 0 as valid numeric baseline)
+ die "tab_column required"
+ unless exists $args{tab_column} && defined $args{tab_column};
+ die "tab_value required"
+ unless exists $args{tab_value};
+
+ my $tab_column = $args{tab_column};
+ my $tab_value = $args{tab_value};
+
+ # fetch actual base vacuum statistics
+ my $cur_value = $node->safe_psql(
+ $dbname,
+ "SELECT $tab_column
+ FROM pg_stat_vacuum_tables
+ WHERE relname = 'vestat';"
+ );
+
+ diag("MISMATCH FOR $tab_column: the current value is $cur_value, while it should be $tab_value");
+}
+
+#------------------------------------------------------------------------------
+# Test 1: Create test table, populate it and run an initial vacuum to force freezing
+#------------------------------------------------------------------------------
+
+$node->safe_psql($dbname, q{
+ 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, 1000) AS g(x);
+ VACUUM (FREEZE, VERBOSE) vestat;
+});
+
+# Poll the stats view until the expected deltas appear or timeout.
+# We do not expect rev_all_* counters to change here, so we pass -1 for them.
+$updated = wait_for_vacuum_stats(
+ tab_frozen_column => 'vm_new_frozen_pages',
+ tab_visible_column => 'vm_new_visible_pages',
+ tab_all_frozen_pages_count => 0,
+ tab_all_visible_pages_count => 0,
+ run_vacuum => 1,
+);
+
+ok($updated,
+ 'vacuum stats updated after vacuuming the table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
+ or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
+
+#------------------------------------------------------------------------------
+# Snapshot current statistics for later comparison
+#------------------------------------------------------------------------------
+
+fetch_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Verify initial statistics after vacuum
+#------------------------------------------------------------------------------
+
+$res = $node->safe_psql($dbname, q{
+ SELECT vm_new_frozen_pages > 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+});
+ok($res eq 't', 'vacuum froze some pages, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
+
+$res = $node->safe_psql($dbname, q{
+ SELECT vm_new_visible_pages > 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+});
+ok($res eq 't', 'vacuum marked pages all-visible, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value =>$vm_new_visible_pages,);
+
+$res = $node->safe_psql($dbname, q{
+ SELECT pg_stat_get_rev_all_frozen_pages(c.oid) = 0
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';
+});
+ok($res eq 't', 'vacuum did not increase frozen-page revision count, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_frozen_pages', tab_value => 0,);
+
+$res = $node->safe_psql($dbname, q{
+ SELECT pg_stat_get_rev_all_visible_pages(c.oid) = 0
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';
+});
+ok($res eq 't', 'vacuum did not increase visible-page revision count, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_visible_pages', tab_value => 0,);
+
+#------------------------------------------------------------------------------
+# Test 2: Trigger backend updates
+# Backend activity should reset per-page visibility/freeze marks and increment revision counters
+#------------------------------------------------------------------------------
+$node->safe_psql($dbname, q{
+ UPDATE vestat SET x = x + 1001;
+});
+
+# Poll until stats update or timeout.
+# We do not expect vm_new_frozen_pages or vm_new_visible_pages to change here,
+# so we pass -1 for those counters.
+$updated = wait_for_vacuum_stats(
+ tab_frozen_column => 'rev_all_frozen_pages',
+ tab_visible_column => 'rev_all_visible_pages',
+ tab_all_frozen_pages_count => 0,
+ tab_all_visible_pages_count => 0,
+ run_vacuum => 0,
+);
+ok($updated,
+ 'vacuum stats updated after backend tuple updates (rev_all_frozen_pages and rev_all_visible_pages advanced)')
+ or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds";
+
+#------------------------------------------------------------------------------
+# Check updated statistics after backend activity
+#------------------------------------------------------------------------------
+
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_frozen_pages = $vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity did not increase the frozen-page count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_visible_pages = $vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity did not increase the all-visible page count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value => $vm_new_visible_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_frozen_pages(c.oid) > $rev_all_frozen_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity increased frozen-page revision count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_frozen_pages', tab_value => $rev_all_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_visible_pages(c.oid) > $rev_all_visible_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'backend activity increased visible-page revision count') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_visible_pages', tab_value => $rev_all_visible_pages,);
+
+#------------------------------------------------------------------------------
+# Update saved snapshots
+#------------------------------------------------------------------------------
+
+fetch_vacuum_stats();
+
+#------------------------------------------------------------------------------
+# Test 3: Force another vacuum after backend modifications - vacuum should restore freeze/visibility
+#------------------------------------------------------------------------------
+
+$node->safe_psql($dbname, q{ VACUUM (FREEZE, VERBOSE) vestat; });
+
+# Poll until stats update or timeout.
+# We pass current snapshot values for vm_new_frozen_pages/vm_new_visible_pages and expect rev counters unchanged.
+$updated = wait_for_vacuum_stats(
+ tab_frozen_column => 'vm_new_frozen_pages',
+ tab_visible_column => 'vm_new_visible_pages',
+ tab_all_frozen_pages_count => $vm_new_frozen_pages,
+ tab_all_visible_pages_count => $vm_new_visible_pages,
+ run_vacuum => 1,
+);
+
+ok($updated,
+ 'vacuum stats updated after vacuuming the all-updated table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
+ or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
+
+#------------------------------------------------------------------------------
+# Verify statistics after final vacuum
+# Check updated stats after backend work
+#------------------------------------------------------------------------------
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_frozen_pages > $vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum froze some pages after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT vm_new_visible_pages > $vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum marked pages all-visible after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value => $vm_new_visible_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_frozen_pages(c.oid) = $rev_all_frozen_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum did not increase frozen-page revision count after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_frozen_pages', tab_value => $rev_all_frozen_pages,);
+
+$res = $node->safe_psql($dbname,
+ "SELECT pg_stat_get_rev_all_visible_pages(c.oid) = $rev_all_visible_pages
+ FROM pg_stat_vacuum_tables vt
+ JOIN pg_class c ON c.relname = vt.relname
+ WHERE vt.relname = 'vestat';"
+);
+ok($res eq 't', 'vacuum did not increase visible-page revision count after backend activity, as expected') or
+ fetch_error_tab_vacuum_statistics(tab_column => 'rev_all_visible_pages', tab_value => $rev_all_visible_pages,);
+
+#------------------------------------------------------------------------------
+# Cleanup
+#------------------------------------------------------------------------------
+
+$node->safe_psql('postgres', q{
+ DROP DATABASE statistic_vacuum_database_regression;
+});
+
+$node->stop;
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 4286c266e17..e4a77878beb 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1844,7 +1844,9 @@ pg_stat_all_tables| SELECT c.oid AS relid,
pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time,
pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time,
pg_stat_get_total_autoanalyze_time(c.oid) AS total_autoanalyze_time,
- pg_stat_get_stat_reset_time(c.oid) AS stats_reset
+ pg_stat_get_stat_reset_time(c.oid) AS stats_reset,
+ pg_stat_get_rev_all_frozen_pages(c.oid) AS rev_all_frozen_pages,
+ pg_stat_get_rev_all_visible_pages(c.oid) AS rev_all_visible_pages
FROM ((pg_class c
LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2266,7 +2268,9 @@ pg_stat_sys_tables| SELECT relid,
total_autovacuum_time,
total_analyze_time,
total_autoanalyze_time,
- stats_reset
+ stats_reset,
+ rev_all_frozen_pages,
+ rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
pg_stat_user_functions| SELECT p.oid AS funcid,
@@ -2321,9 +2325,43 @@ pg_stat_user_tables| SELECT relid,
total_autovacuum_time,
total_analyze_time,
total_autoanalyze_time,
- stats_reset
+ stats_reset,
+ rev_all_frozen_pages,
+ rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
+ rel.relname,
+ stats.relid,
+ stats.total_blks_read,
+ stats.total_blks_hit,
+ stats.total_blks_dirtied,
+ stats.total_blks_written,
+ stats.rel_blks_read,
+ stats.rel_blks_hit,
+ stats.pages_scanned,
+ stats.pages_removed,
+ stats.vm_new_frozen_pages,
+ stats.vm_new_visible_pages,
+ stats.vm_new_visible_frozen_pages,
+ stats.missed_dead_pages,
+ stats.tuples_deleted,
+ stats.tuples_frozen,
+ stats.recently_dead_tuples,
+ stats.missed_dead_tuples,
+ stats.wraparound_failsafe,
+ stats.index_vacuum_count,
+ stats.wal_records,
+ stats.wal_fpi,
+ stats.wal_bytes,
+ stats.blk_read_time,
+ stats.blk_write_time,
+ stats.delay_time,
+ stats.total_time
+ FROM (pg_class rel
+ JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
+ LATERAL pg_stat_get_vacuum_tables(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (rel.relkind = 'r'::"char");
pg_stat_wal| SELECT wal_records,
wal_fpi,
wal_bytes,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 905f9bca959..62f2ac11659 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -139,4 +139,4 @@ test: fast_default
# run tablespace test at the end because it drops the tablespace created during
# setup that other tests may use.
-test: tablespace
+test: tablespace
\ No newline at end of file
--
2.39.5 (Apple Git-154)
[text/plain] v26-0002-Machinery-for-grabbing-an-extended-vacuum-statistics.patch (54.6K, 4-v26-0002-Machinery-for-grabbing-an-extended-vacuum-statistics.patch)
download | inline diff:
From d09c2f688fc8776b239a39f0cd9cda5488dba812 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Tue, 9 Dec 2025 10:56:54 +0300
Subject: [PATCH 2/5] Machinery for grabbing an extended vacuum statistics on
index relations.
They are gathered separatelly from table statistics.
As for tables, we gather vacuum shared buffers statistics for index relations like
value of total_blks_hit, total_blks_read, total_blks_dirtied, wal statistics, io time
during flushing buffer pages to disk, delay and total time.
Due to the fact that such statistics are common as for tables, as for indexes we
set them in the union ExtVacReport structure. We only added some determination 'type'
field to highlight what kind belong to these statistics: PGSTAT_EXTVAC_TABLE or
PGSTAT_EXTVAC_INDEX. Generally, PGSTAT_EXTVAC_INVALID type leads to wrong code process.
Some statistics belong only one type of both tables or indexes. So, we added substructures
sych table and index inside ExtVacReport structure.
Therefore, we gather only for tables such statistics like number of scanned, removed pages,
their charecteristics according VM (all-visible and frozen). In addition, for tables we
gather number frozen, deleted and recently dead tuples and how many times vacuum processed
indexes for tables.
Controversally for indexes we gather number of deleted pages and deleted tuples only.
As for tables, deleted pages and deleted tuples reflect the overall performance of the vacuum
for the index relationship.
Since the vacuum cleans up references to tuple indexes before cleaning up table tuples,
which adds some complexity to the vacuum process, namely the vacuum switches from cleaning up
a table to its indexes and back during its operation, we need to save the vacuum statistics
collected for the heap before it starts cleaning up the indexes.
That's why it's necessary to track the vacuum statistics for the heap several times during
the vacuum procedure. To avoid sending the statistics to the Cumulative Statistics System
several times, we save these statistics in the LVRelState structure and only after vacuum
finishes cleaning up the heap, it sends them to the Cumulative Statistics System.
Authors: Alena Rybakina <[email protected]>,
Andrei Lepikhov <[email protected]>,
Andrei Zubkov <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>, Masahiko Sawada <[email protected]>,
Ilia Evdokimov <[email protected]>, jian he <[email protected]>,
Kirill Reshke <[email protected]>, Alexander Korotkov <[email protected]>,
Jim Nasby <[email protected]>, Sami Imseih <[email protected]>,
Karina Litskevich <[email protected]>
---
src/backend/access/heap/vacuumlazy.c | 232 +++++++++++++----
src/backend/catalog/system_views.sql | 32 +++
src/backend/commands/vacuumparallel.c | 10 +
src/backend/utils/activity/pgstat_relation.c | 45 ++--
src/backend/utils/adt/pgstatfuncs.c | 92 ++++++-
src/include/catalog/pg_proc.dat | 9 +
src/include/commands/vacuum.h | 25 ++
src/include/pgstat.h | 77 ++++--
.../vacuum-extending-in-repetable-read.out | 4 +-
.../t/050_vacuum_extending_basic_test.pl | 237 +++++++++++++++++-
src/test/regress/expected/rules.out | 22 ++
11 files changed, 681 insertions(+), 104 deletions(-)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 66e09d0a0cf..719ce90d96d 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -290,6 +290,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 */
@@ -412,6 +413,7 @@ typedef struct LVRelState
int32 wraparound_failsafe_count; /* number of emergency vacuums to
* prevent anti-wraparound
* shutdown */
+ ExtVacReport extVacReportIdx;
} LVRelState;
@@ -423,19 +425,6 @@ typedef struct LVSavedErrInfo
VacErrPhase phase;
} LVSavedErrInfo;
-/*
- * Counters and usage data for extended stats tracking.
- */
-typedef struct LVExtStatCounters
-{
- TimestampTz starttime;
- 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);
static void heap_vacuum_eager_scan_setup(LVRelState *vacrel,
@@ -565,27 +554,25 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
endtime = GetCurrentTimestamp();
TimestampDifference(counters->starttime, 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->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->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.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;
+ 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;
- report->total_time = secs * 1000. + usecs / 1000.;
+ report->total_time += secs * 1000. + usecs / 1000.;
if (!rel->pgstat_info || !pgstat_track_counts)
@@ -595,12 +582,122 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
*/
return;
- report->blks_fetched =
+ report->blks_fetched +=
rel->pgstat_info->counts.blocks_fetched - counters->blocks_fetched;
- report->blks_hit =
+ report->blks_hit +=
rel->pgstat_info->counts.blocks_hit - counters->blocks_hit;
}
+void
+extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters)
+{
+ /* Set initial values for common heap and index statistics */
+ 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;
+ }
+}
+
+void
+extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters, ExtVacReport * report)
+{
+ memset(report, 0, sizeof(ExtVacReport));
+
+ 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->tuples_deleted =
+ stats->tuples_removed - counters->tuples_removed;
+ report->index.pages_deleted =
+ stats->pages_deleted - counters->pages_deleted;
+ }
+}
+
+/* Accumulate vacuum statistics for heap.
+ *
+ * Because of complexity of vacuum processing: it switch procesing between
+ * the heap relation to index relations and visa versa, we need to store
+ * gathered statistics information for heap relations several times before
+ * the vacuum starts processing the indexes again.
+ *
+ * It is necessary to gather correct statistics information for heap and indexes
+ * otherwice the index statistics information would be added to his parent heap
+ * statistics information and it would be difficult to analyze it later.
+ *
+ * We can't subtract union vacuum statistics information for index from the heap relations
+ * because of total and delay time time statistics collecting during parallel vacuum
+ * procudure.
+*/
+static void
+accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats)
+{
+ /* Fill heap-specific extended stats fields */
+ extVacStats->type = PGSTAT_EXTVAC_TABLE;
+ extVacStats->table.pages_scanned = vacrel->scanned_pages;
+ extVacStats->table.pages_removed = vacrel->removed_pages;
+ extVacStats->table.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
+ extVacStats->table.vm_new_visible_pages = vacrel->vm_new_visible_pages;
+ extVacStats->table.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
+ extVacStats->tuples_deleted = vacrel->tuples_deleted;
+ extVacStats->table.tuples_frozen = vacrel->tuples_frozen;
+ extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
+ extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
+ extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
+ extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
+ extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
+ extVacStats->table.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+
+ extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
+ extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
+ extVacStats->total_blks_dirtied -= vacrel->extVacReportIdx.total_blks_dirtied;
+ extVacStats->total_blks_hit -= vacrel->extVacReportIdx.total_blks_hit;
+ extVacStats->total_blks_read -= vacrel->extVacReportIdx.total_blks_read;
+ extVacStats->total_blks_written -= vacrel->extVacReportIdx.total_blks_written;
+ extVacStats->wal_bytes -= vacrel->extVacReportIdx.wal_bytes;
+ extVacStats->wal_fpi -= vacrel->extVacReportIdx.wal_fpi;
+ extVacStats->wal_records -= vacrel->extVacReportIdx.wal_records;
+
+ extVacStats->total_time -= vacrel->extVacReportIdx.total_time;
+ extVacStats->delay_time -= vacrel->extVacReportIdx.delay_time;
+
+}
+
+static void
+accumulate_idxs_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacIdxStats)
+{
+ /* Fill heap-specific extended stats fields */
+ vacrel->extVacReportIdx.blk_read_time += extVacIdxStats->blk_read_time;
+ vacrel->extVacReportIdx.blk_write_time += extVacIdxStats->blk_write_time;
+ vacrel->extVacReportIdx.total_blks_dirtied += extVacIdxStats->total_blks_dirtied;
+ vacrel->extVacReportIdx.total_blks_hit += extVacIdxStats->total_blks_hit;
+ vacrel->extVacReportIdx.total_blks_read += extVacIdxStats->total_blks_read;
+ vacrel->extVacReportIdx.total_blks_written += extVacIdxStats->total_blks_written;
+ vacrel->extVacReportIdx.wal_bytes += extVacIdxStats->wal_bytes;
+ vacrel->extVacReportIdx.wal_fpi += extVacIdxStats->wal_fpi;
+ vacrel->extVacReportIdx.wal_records += extVacIdxStats->wal_records;
+ vacrel->extVacReportIdx.delay_time += extVacIdxStats->delay_time;
+
+ vacrel->extVacReportIdx.total_time += extVacIdxStats->total_time;
+}
+
/*
* Helper to set up the eager scanning state for vacuuming a single relation.
@@ -760,11 +857,9 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
char **indnames = NULL;
LVExtStatCounters extVacCounters;
ExtVacReport extVacReport;
- ExtVacReport allzero;
/* Initialize vacuum statistics */
- memset(&allzero, 0, sizeof(ExtVacReport));
- extVacReport = allzero;
+ memset(&extVacReport, 0, sizeof(ExtVacReport));
verbose = (params.options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -820,6 +915,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
+ memset(&vacrel->extVacReportIdx, 0, sizeof(ExtVacReport));
+
/* Set up high level stuff about rel and its indexes */
vacrel->rel = rel;
vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
@@ -1078,20 +1175,6 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
/* 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.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
- extVacReport.vm_new_visible_pages = vacrel->vm_new_visible_pages;
- extVacReport.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
- extVacReport.tuples_deleted = vacrel->tuples_deleted;
- extVacReport.tuples_frozen = vacrel->tuples_frozen;
- extVacReport.recently_dead_tuples = vacrel->recently_dead_tuples;
- extVacReport.missed_dead_tuples = vacrel->missed_dead_tuples;
- extVacReport.missed_dead_pages = vacrel->missed_dead_pages;
- extVacReport.index_vacuum_count = vacrel->num_index_scans;
- extVacReport.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
-
/*
* Report results to the cumulative stats system, too.
*
@@ -1102,6 +1185,13 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
* soon in cases where the failsafe prevented significant amounts of heap
* vacuuming.
*/
+
+ /*
+ * Make generic extended vacuum stats report and fill heap-specific
+ * extended stats fields.
+ */
+ extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport);
+ accumulate_heap_vacuum_statistics(vacrel, &extVacReport);
pgstat_report_vacuum(rel,
Max(vacrel->new_live_tuples, 0),
vacrel->recently_dead_tuples +
@@ -2811,10 +2901,20 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
}
else
{
+ LVExtStatCounters counters;
+ ExtVacReport extVacReport;
+
+ memset(&extVacReport, 0, sizeof(ExtVacReport));
+
+ extvac_stats_start(vacrel->rel, &counters);
+
/* Outsource everything to parallel variant */
parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples,
vacrel->num_index_scans);
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
+
/*
* Do a postcheck to consider applying wraparound failsafe now. Note
* that parallel VACUUM only gets the precheck and this postcheck.
@@ -3244,10 +3344,20 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
}
else
{
+ LVExtStatCounters counters;
+ ExtVacReport extVacReport;
+
+ memset(&extVacReport, 0, sizeof(ExtVacReport));
+
+ extvac_stats_start(vacrel->rel, &counters);
+
/* Outsource everything to parallel variant */
parallel_vacuum_cleanup_all_indexes(vacrel->pvs, reltuples,
vacrel->num_index_scans,
estimated_count);
+
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
}
/* Reset the progress counters */
@@ -3273,6 +3383,11 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
{
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
+ LVExtStatCountersIdx extVacCounters;
+ ExtVacReport extVacReport;
+
+ /* Set initial statistics values to gather vacuum statistics for the index */
+ extvac_stats_start_idx(indrel, istat, &extVacCounters);
ivinfo.index = indrel;
ivinfo.heaprel = vacrel->rel;
@@ -3291,6 +3406,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);
@@ -3299,6 +3415,15 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
istat = vac_bulkdel_one_index(&ivinfo, istat, vacrel->dead_items,
vacrel->dead_items_info);
+ /* Make extended vacuum stats report for index */
+ extvac_stats_end_idx(indrel, istat, &extVacCounters, &extVacReport);
+
+ if (!ParallelVacuumIsActive(vacrel))
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
+
+ pgstat_report_vacuum(indrel,
+ 0, 0, 0, &extVacReport);
+
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
pfree(vacrel->indname);
@@ -3323,6 +3448,11 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
{
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
+ LVExtStatCountersIdx extVacCounters;
+ ExtVacReport extVacReport;
+
+ /* Set initial statistics values to gather vacuum statistics for the index */
+ extvac_stats_start_idx(indrel, istat, &extVacCounters);
ivinfo.index = indrel;
ivinfo.heaprel = vacrel->rel;
@@ -3342,12 +3472,22 @@ 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);
+
+ if (!ParallelVacuumIsActive(vacrel))
+ accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
+
+ pgstat_report_vacuum(indrel,
+ 0, 0, 0, &extVacReport);
+
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
pfree(vacrel->indname);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ffb407d414f..47b6a00d297 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1502,3 +1502,35 @@ 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';
+
+CREATE VIEW pg_stat_vacuum_indexes AS
+SELECT
+ rel.oid as relid,
+ ns.nspname AS schemaname,
+ rel.relname AS relname,
+
+ total_blks_read AS total_blks_read,
+ total_blks_hit AS total_blks_hit,
+ total_blks_dirtied AS total_blks_dirtied,
+ total_blks_written AS total_blks_written,
+
+ rel_blks_read AS rel_blks_read,
+ rel_blks_hit AS rel_blks_hit,
+
+ pages_deleted AS pages_deleted,
+ tuples_deleted AS tuples_deleted,
+
+ wal_records AS wal_records,
+ wal_fpi AS wal_fpi,
+ wal_bytes AS wal_bytes,
+
+ blk_read_time AS blk_read_time,
+ blk_write_time AS blk_write_time,
+
+ delay_time AS delay_time,
+ total_time AS total_time
+FROM
+ pg_class rel
+ JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
+ LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
+WHERE rel.relkind = 'i';
\ No newline at end of file
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 114cd7c31d3..43450685b09 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -868,6 +868,8 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
IndexBulkDeleteResult *istat = NULL;
IndexBulkDeleteResult *istat_res;
IndexVacuumInfo ivinfo;
+ LVExtStatCountersIdx extVacCounters;
+ ExtVacReport extVacReport;
/*
* Update the pointer to the corresponding bulk-deletion result if someone
@@ -876,6 +878,9 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
if (indstats->istat_updated)
istat = &(indstats->istat);
+ /* Set initial statistics values to gather vacuum statistics for the index */
+ extvac_stats_start_idx(indrel, &(indstats->istat), &extVacCounters);
+
ivinfo.index = indrel;
ivinfo.heaprel = pvs->heaprel;
ivinfo.analyze_only = false;
@@ -904,6 +909,11 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
RelationGetRelationName(indrel));
}
+ /* Make extended vacuum stats report for index */
+ extvac_stats_end_idx(indrel, istat_res, &extVacCounters, &extVacReport);
+ pgstat_report_vacuum(indrel,
+ 0, 0, 0, &extVacReport);
+
/*
* Copy the index bulk-deletion result returned from ambulkdelete and
* amvacuumcleanup to the DSM segment if it's the first cycle because they
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 361713479e8..4bd6afc3794 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -1036,20 +1036,35 @@ 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->vm_new_frozen_pages += src->vm_new_frozen_pages;
- dst->vm_new_visible_pages += src->vm_new_visible_pages;
- dst->vm_new_visible_frozen_pages += src->vm_new_visible_frozen_pages;
- dst->tuples_deleted += src->tuples_deleted;
- dst->tuples_frozen += src->tuples_frozen;
- dst->recently_dead_tuples += src->recently_dead_tuples;
- dst->index_vacuum_count += src->index_vacuum_count;
- dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
- dst->missed_dead_pages += src->missed_dead_pages;
- dst->missed_dead_tuples += src->missed_dead_tuples;
+ if (dst->type == PGSTAT_EXTVAC_INVALID)
+ dst->type = src->type;
+ Assert(src->type == PGSTAT_EXTVAC_INVALID || src->type == dst->type);
+
+ if (dst->type == src->type)
+ {
+ dst->blks_fetched += src->blks_fetched;
+ dst->blks_hit += src->blks_hit;
+
+ if (dst->type == PGSTAT_EXTVAC_TABLE)
+ {
+ dst->table.pages_scanned += src->table.pages_scanned;
+ dst->table.pages_removed += src->table.pages_removed;
+ dst->table.vm_new_frozen_pages += src->table.vm_new_frozen_pages;
+ dst->table.vm_new_visible_pages += src->table.vm_new_visible_pages;
+ dst->table.vm_new_visible_frozen_pages += src->table.vm_new_visible_frozen_pages;
+ dst->tuples_deleted += src->tuples_deleted;
+ dst->table.tuples_frozen += src->table.tuples_frozen;
+ dst->table.recently_dead_tuples += src->table.recently_dead_tuples;
+ dst->table.index_vacuum_count += src->table.index_vacuum_count;
+ dst->table.missed_dead_pages += src->table.missed_dead_pages;
+ dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
+ dst->table.wraparound_failsafe_count += src->table.wraparound_failsafe_count;
+ }
+ else if (dst->type == PGSTAT_EXTVAC_INDEX)
+ {
+ dst->index.pages_deleted += src->index.pages_deleted;
+ dst->tuples_deleted += src->tuples_deleted;
+ }
+ }
}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index d7dfda0c1a7..755751c3b46 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2360,18 +2360,19 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
extvacuum->blks_hit);
values[i++] = Int64GetDatum(extvacuum->blks_hit);
- values[i++] = Int64GetDatum(extvacuum->pages_scanned);
- values[i++] = Int64GetDatum(extvacuum->pages_removed);
- values[i++] = Int64GetDatum(extvacuum->vm_new_frozen_pages);
- values[i++] = Int64GetDatum(extvacuum->vm_new_visible_pages);
- values[i++] = Int64GetDatum(extvacuum->vm_new_visible_frozen_pages);
- values[i++] = Int64GetDatum(extvacuum->missed_dead_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.pages_scanned);
+ values[i++] = Int64GetDatum(extvacuum->table.pages_removed);
+ values[i++] = Int64GetDatum(extvacuum->table.vm_new_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_frozen_pages);
+ values[i++] = Int64GetDatum(extvacuum->table.missed_dead_pages);
values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
- values[i++] = Int64GetDatum(extvacuum->tuples_frozen);
- values[i++] = Int64GetDatum(extvacuum->recently_dead_tuples);
- values[i++] = Int64GetDatum(extvacuum->missed_dead_tuples);
- values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
- values[i++] = Int64GetDatum(extvacuum->index_vacuum_count);
+ values[i++] = Int64GetDatum(extvacuum->table.tuples_frozen);
+ values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
+ values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
+
+ values[i++] = Int32GetDatum(extvacuum->table.wraparound_failsafe_count);
+ values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
values[i++] = Int64GetDatum(extvacuum->wal_records);
values[i++] = Int64GetDatum(extvacuum->wal_fpi);
@@ -2393,3 +2394,72 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+/*
+ * Get the vacuum statistics for the heap tables.
+ */
+Datum
+pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 16
+
+ Oid relid = PG_GETARG_OID(0);
+ PgStat_StatTabEntry *tabentry;
+ ExtVacReport *extvacuum;
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
+ char buf[256];
+ int i = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tabentry = pgstat_fetch_stat_tabentry(relid);
+
+ if (tabentry == NULL)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
+ else
+ {
+ extvacuum = &(tabentry->vacuum_ext);
+ }
+
+ i = 0;
+
+ values[i++] = ObjectIdGetDatum(relid);
+
+ values[i++] = Int64GetDatum(extvacuum->total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+
+ values[i++] = Int64GetDatum(extvacuum->blks_fetched -
+ extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->blks_hit);
+
+ values[i++] = Int64GetDatum(extvacuum->index.pages_deleted);
+ values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+
+ values[i++] = Int64GetDatum(extvacuum->wal_records);
+ values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+ /* Convert to numeric, like pg_stat_statements */
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ values[i++] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ values[i++] = Float8GetDatum(extvacuum->blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->delay_time);
+ values[i++] = Float8GetDatum(extvacuum->total_time);
+
+ Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS);
+
+ /* Returns the record as Datum */
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 915a5a7822f..e957781b623 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12630,4 +12630,13 @@
proname => 'pg_stat_get_rev_all_frozen_pages', provolatile => 's',
proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
prosrc => 'pg_stat_get_rev_all_frozen_pages' },
+{ oid => '8004',
+ descr => 'pg_stat_get_vacuum_indexes return stats values',
+ proname => 'pg_stat_get_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
+ proretset => 't',
+ proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_deleted,tuples_deleted,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time}',
+ prosrc => 'pg_stat_get_vacuum_indexes' }
]
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 6b997bc7fb1..b48ace6084b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -25,6 +25,7 @@
#include "storage/buf.h"
#include "storage/lock.h"
#include "utils/relcache.h"
+#include "pgstat.h"
/*
* Flags for amparallelvacuumoptions to control the participation of bulkdelete
@@ -300,6 +301,26 @@ typedef struct VacDeadItemsInfo
int64 num_items; /* current # of entries */
} VacDeadItemsInfo;
+/*
+ * Counters and usage data for extended stats tracking.
+ */
+typedef struct LVExtStatCounters
+{
+ TimestampTz starttime;
+ WalUsage walusage;
+ BufferUsage bufusage;
+ double VacuumDelayTime;
+ PgStat_Counter blocks_fetched;
+ PgStat_Counter blocks_hit;
+} LVExtStatCounters;
+
+typedef struct LVExtStatCountersIdx
+{
+ LVExtStatCounters common;
+ int64 pages_deleted;
+ int64 tuples_removed;
+} LVExtStatCountersIdx;
+
/* GUC parameters */
extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for PostGIS */
extern PGDLLIMPORT int vacuum_freeze_min_age;
@@ -413,4 +434,8 @@ extern double anl_random_fract(void);
extern double anl_init_selection_state(int n);
extern double anl_get_next_S(double t, int n, double *stateptr);
+extern void extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters);
+extern void extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
+ LVExtStatCountersIdx * counters, ExtVacReport * report);
#endif /* VACUUM_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 46d12fa3bd0..f2881dbb6f9 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -114,11 +114,19 @@ typedef struct PgStat_BackendSubEntry
PgStat_Counter conflict_count[CONFLICT_NUM_TYPES];
} PgStat_BackendSubEntry;
+/* Type of ExtVacReport */
+typedef enum ExtVacReportType
+{
+ PGSTAT_EXTVAC_INVALID = 0,
+ PGSTAT_EXTVAC_TABLE = 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
@@ -155,23 +163,58 @@ typedef struct ExtVacReport
* point, in msec */
double total_time; /* total time of a vacuum operation, in msec */
- int64 pages_scanned; /* heap pages examined (not skipped by VM) */
- int64 pages_removed; /* heap pages removed by vacuum "truncation" */
- int64 vm_new_frozen_pages; /* pages marked in VM as frozen */
- int64 vm_new_visible_pages; /* pages marked in VM as all-visible */
- int64 vm_new_visible_frozen_pages; /* pages marked in VM as
- * all-visible and frozen */
- int64 missed_dead_tuples; /* tuples not pruned by vacuum due to
- * failure to get a cleanup lock */
- int64 missed_dead_pages; /* pages with missed dead tuples */
int64 tuples_deleted; /* tuples deleted by vacuum */
- int64 tuples_frozen; /* tuples frozen up by vacuum */
- int64 recently_dead_tuples; /* deleted tuples that are still
- * visible to some transaction */
- int64 index_vacuum_count; /* the number of index vacuumings */
- int32 wraparound_failsafe_count; /* number of emergency vacuums to
- * prevent anti-wraparound
- * shutdown */
+
+ 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; /* heap pages examined (not skipped by
+ * VM) */
+ int64 pages_removed; /* heap pages removed by vacuum
+ * "truncation" */
+ int64 pages_frozen; /* pages marked in VM as frozen */
+ int64 pages_all_visible; /* pages marked in VM as
+ * all-visible */
+ int64 tuples_frozen; /* tuples frozen up by vacuum */
+ int64 recently_dead_tuples; /* deleted tuples that are
+ * still visible to some
+ * transaction */
+ int64 vm_new_frozen_pages; /* pages marked in VM as
+ * frozen */
+ int64 vm_new_visible_pages; /* pages marked in VM as
+ * all-visible */
+ int64 vm_new_visible_frozen_pages; /* pages marked in VM as
+ * all-visible and
+ * frozen */
+ int64 missed_dead_tuples; /* tuples not pruned by vacuum due
+ * to failure to get a cleanup
+ * lock */
+ int64 missed_dead_pages; /* pages with missed dead tuples */
+ int64 index_vacuum_count; /* number of index vacuumings */
+ int32 wraparound_failsafe_count; /* number of emergency
+ * vacuums to prevent
+ * anti-wraparound
+ * shutdown */
+ } table;
+ struct
+ {
+ int64 pages_deleted; /* number of pages deleted by vacuum */
+ } index;
+ } /* per_type_stats */ ;
} ExtVacReport;
/* ----------
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 87f7e40b4a6..6d960423912 100644
--- a/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
+++ b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out
@@ -34,7 +34,7 @@ step s2_print_vacuum_stats_table:
relname |tuples_deleted|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
--------------------------+--------------+--------------------+------------------+-----------------+-------------
-test_vacuum_stat_isolation| 0| 100| 0| 0| 0
+test_vacuum_stat_isolation| 0| 600| 0| 0| 0
(1 row)
step s1_commit: COMMIT;
@@ -48,6 +48,6 @@ step s2_print_vacuum_stats_table:
relname |tuples_deleted|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen
--------------------------+--------------+--------------------+------------------+-----------------+-------------
-test_vacuum_stat_isolation| 100| 100| 0| 0| 101
+test_vacuum_stat_isolation| 300| 600| 0| 0| 303
(1 row)
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index 7e25a3fe63f..8f7b1e2909b 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -2,9 +2,10 @@
# Test cumulative vacuum stats system using TAP
#
# This test validates the accuracy and behavior of cumulative vacuum statistics
-# across tables using:
+# across heap tables, indexes using:
#
# • pg_stat_vacuum_tables
+# • pg_stat_vacuum_indexes
#
# A polling helper function repeatedly checks the stats views until expected
# deltas appear or a configurable timeout expires. This guarantees that
@@ -62,7 +63,7 @@ $node->safe_psql($dbname, q{
$node->safe_psql(
$dbname,
- "CREATE TABLE vestat (x int)
+ "CREATE TABLE vestat (x int PRIMARY KEY)
WITH (autovacuum_enabled = off, fillfactor = 10);
INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
ANALYZE vestat;"
@@ -80,12 +81,15 @@ my $updated = 0;
#------------------------------------------------------------------------------
# wait_for_vacuum_stats
#
-# Polls pg_stat_vacuum_tables until the table-level counters exceed
-# the provided baselines, or until the configured timeout elapses.
+# Polls pg_stat_vacuum_tables and pg_stat_vacuum_indexes until both the
+# table-level and index-level counters exceed the provided baselines, or until
+# the configured timeout elapses.
#
# Expected named args (baseline values):
# tab_tuples_deleted
# tab_wal_records
+# idx_tuples_deleted
+# idx_wal_records
#
# Returns: 1 if the condition is met before timeout, 0 otherwise.
#------------------------------------------------------------------------------
@@ -94,6 +98,8 @@ sub wait_for_vacuum_stats {
my (%args) = @_;
my $tab_tuples_deleted = $args{tab_tuples_deleted} or 0;
my $tab_wal_records = $args{tab_wal_records} or 0;
+ my $idx_tuples_deleted = $args{idx_tuples_deleted} or 0;
+ my $idx_wal_records = $args{idx_wal_records} or 0;
my $start = time();
while ((time() - $start) < $timeout) {
@@ -101,9 +107,14 @@ sub wait_for_vacuum_stats {
my $result_query = $node->safe_psql(
$dbname,
"VACUUM vestat;
- SELECT tuples_deleted > $tab_tuples_deleted AND wal_records > $tab_wal_records
+ SELECT
+ (SELECT (tuples_deleted > $tab_tuples_deleted AND wal_records > $tab_wal_records)
FROM pg_stat_vacuum_tables
- WHERE relname = 'vestat';"
+ WHERE relname = 'vestat')
+ AND
+ (SELECT (tuples_deleted > $idx_tuples_deleted AND wal_records > $idx_wal_records)
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey');"
);
return 1 if ($result_query eq 't');
@@ -126,6 +137,12 @@ my $wal_records = 0;
my $wal_bytes = 0;
my $wal_fpi = 0;
+my $index_tuples_deleted = 0;
+my $index_pages_deleted = 0;
+my $index_wal_records = 0;
+my $index_wal_bytes = 0;
+my $index_wal_fpi = 0;
+
my $pages_frozen_prev = 0;
my $tuples_deleted_prev = 0;
my $pages_scanned_prev = 0;
@@ -134,11 +151,17 @@ my $wal_records_prev = 0;
my $wal_bytes_prev = 0;
my $wal_fpi_prev = 0;
+my $index_tuples_deleted_prev = 0;
+my $index_pages_deleted_prev = 0;
+my $index_wal_records_prev = 0;
+my $index_wal_bytes_prev = 0;
+my $index_wal_fpi_prev = 0;
+
#------------------------------------------------------------------------------
# fetch_vacuum_stats
#
-# Reads current values of relevant vacuum counters for the test table,
-# storing them in package variables for subsequent comparisons.
+# Reads current values of relevant vacuum counters for the test table and its
+# primary index, storing them in package variables for subsequent comparisons.
#------------------------------------------------------------------------------
sub fetch_vacuum_stats {
@@ -153,6 +176,18 @@ sub fetch_vacuum_stats {
$base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
($pages_frozen, $tuples_deleted, $pages_scanned, $pages_removed, $wal_records, $wal_bytes, $wal_fpi)
= split /\s+/, $base_statistics;
+
+ # --- index stats ---
+ my $index_base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT tuples_deleted, pages_deleted, wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+ );
+
+ $index_base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
+ ($index_tuples_deleted, $index_pages_deleted, $index_wal_records, $index_wal_bytes, $index_wal_fpi)
+ = split /\s+/, $index_base_statistics;
}
#------------------------------------------------------------------------------
@@ -169,6 +204,12 @@ sub save_vacuum_stats {
$wal_records_prev = $wal_records;
$wal_bytes_prev = $wal_bytes;
$wal_fpi_prev = $wal_fpi;
+
+ $index_tuples_deleted_prev = $index_tuples_deleted;
+ $index_pages_deleted_prev = $index_pages_deleted;
+ $index_wal_records_prev = $index_wal_records;
+ $index_wal_bytes_prev = $index_wal_bytes;
+ $index_wal_fpi_prev = $index_wal_fpi;
}
#------------------------------------------------------------------------------
@@ -195,7 +236,20 @@ sub print_vacuum_stats_on_error {
" pages_removed = $pages_removed\n" .
" wal_records = $wal_records\n" .
" wal_bytes = $wal_bytes\n" .
- " wal_fpi = $wal_fpi\n"
+ " wal_fpi = $wal_fpi\n" .
+ "Index statistics:\n" .
+ " Before test:\n" .
+ " tuples_deleted = $index_tuples_deleted_prev\n" .
+ " pages_removed = $index_pages_deleted_prev\n" .
+ " wal_records = $index_wal_records_prev\n" .
+ " wal_bytes = $index_wal_bytes_prev\n" .
+ " wal_fpi = $index_wal_fpi_prev\n" .
+ " After test:\n" .
+ " tuples_deleted = $index_tuples_deleted\n" .
+ " pages_removed = $index_pages_deleted\n" .
+ " wal_records = $index_wal_records\n" .
+ " wal_bytes = $index_wal_bytes\n" .
+ " wal_fpi = $index_wal_fpi\n"
);
};
@@ -203,7 +257,8 @@ sub print_vacuum_stats_on_error {
# fetch_vacuum_stats during mismatch
#
# Print current values and old values of relevant vacuum counters for the test
-# table, storing them in package variables for subsequent comparisons.
+# table and its primary index, storing them in package variables for subsequent
+# comparisons.
#------------------------------------------------------------------------------
sub fetch_error_base_tab_vacuum_statistics {
@@ -258,6 +313,54 @@ sub fetch_error_wal_tab_vacuum_statistics {
);
}
+sub fetch_error_base_idx_vacuum_statistics {
+
+ # fetch actual base vacuum statistics
+ my $base_statistics = $node->safe_psql(
+ $dbname,
+ "SELECT tuples_deleted, pages_deleted
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+ );
+ $base_statistics =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_tuples_deleted, $cur_pages_deleted) = split /\s+/, $base_statistics;
+
+ diag(
+ "BASE STATS MISMATCH FOR INDEX:\n" .
+ " Baseline:\n" .
+ " tuples_deleted = $index_tuples_deleted\n" .
+ " pages_removed = $index_pages_deleted\n" .
+ " Current:\n" .
+ " tuples_deleted = $cur_tuples_deleted\n" .
+ " pages_deleted = $cur_pages_deleted\n"
+ );
+}
+
+sub fetch_error_wal_idx_vacuum_statistics {
+
+ my $wal_raw = $node->safe_psql(
+ $dbname,
+ "SELECT wal_records, wal_bytes, wal_fpi
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+ );
+
+ $wal_raw =~ s/\s*\|\s*/ /g; # transform " | " in space
+ my ($cur_wal_rec, $cur_wal_bytes, $cur_wal_fpi) = split /\s+/, $wal_raw;
+
+ diag(
+ "WAL STATS MISMATCH FOR INDEX:\n" .
+ " Baseline:\n" .
+ " wal_records = $index_wal_records\n" .
+ " wal_bytes = $index_wal_bytes\n" .
+ " wal_fpi = $index_wal_fpi\n" .
+ " Current:\n" .
+ " wal_records = $cur_wal_rec\n" .
+ " wal_bytes = $cur_wal_bytes\n" .
+ " wal_fpi = $cur_wal_fpi\n"
+ );
+}
+
#------------------------------------------------------------------------------
# Test 1: Delete half the rows, run VACUUM, and wait for stats to advance
#------------------------------------------------------------------------------
@@ -270,7 +373,9 @@ $node->safe_psql($dbname, "VACUUM vestat;");
# Poll the stats view until expected deltas appear or timeout
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => 0,
- tab_wal_records => 0
+ tab_wal_records => 0,
+ idx_tuples_deleted => 0,
+ idx_wal_records => 0,
);
ok($updated, 'vacuum stats updated after vacuuming half-deleted table (tuples_deleted and wal_fpi advanced)')
or diag "Timeout waiting for pg_stats_vacuum_* update after $timeout seconds after vacuuming half-deleted table";
@@ -290,6 +395,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted > $index_tuples_deleted_prev, 'index tuples_deleted has increased');
+ok($index_wal_records > $index_wal_records_prev, 'index wal_records has increased');
+ok($index_wal_bytes > $index_wal_bytes_prev, 'index wal_bytes has increased');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -307,6 +418,8 @@ $node->safe_psql($dbname, "VACUUM vestat;");
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => $tuples_deleted_prev,
tab_wal_records => $wal_records_prev,
+ idx_tuples_deleted => $index_tuples_deleted_prev,
+ idx_wal_records => $index_wal_records_prev,
);
ok($updated, 'vacuum stats updated after vacuuming all-deleted table (tuples_deleted and wal_records advanced)')
@@ -327,6 +440,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted > $index_pages_deleted_prev, 'index pages_deleted has increased');
+ok($index_tuples_deleted > $index_tuples_deleted_prev, 'index tuples_deleted has increased');
+ok($index_wal_records > $index_wal_records_prev, 'index wal_records has increased');
+ok($index_wal_bytes > $index_wal_bytes_prev, 'index wal_bytes has increased');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -357,6 +476,12 @@ ok($wal_records == $wal_records_prev, 'table wal_records stay the same');
ok($wal_bytes == $wal_bytes_prev, 'table wal_bytes stay the same');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted == $index_tuples_deleted_prev, 'index tuples_deleted stay the same');
+ok($index_wal_records == $index_wal_records_prev, 'index wal_records stay the same');
+ok($index_wal_bytes == $index_wal_bytes_prev, 'index wal_bytes stay the same');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -379,6 +504,8 @@ $node->safe_psql(
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => $tuples_deleted,
tab_wal_records => $wal_records,
+ idx_tuples_deleted => $index_tuples_deleted,
+ idx_wal_records => $index_wal_records,
);
ok($updated, 'vacuum stats updated after updating tuples in the table (tuples_deleted and wal_records advanced)')
@@ -399,6 +526,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi > $wal_fpi_prev, 'table wal_fpi has increased');
+ok($index_pages_deleted > $index_pages_deleted_prev, 'index pages_deleted has increased');
+ok($index_tuples_deleted > $index_tuples_deleted_prev, 'index tuples_deleted has increased');
+ok($index_wal_records > $index_wal_records_prev, 'index wal_records has increased');
+ok($index_wal_bytes > $index_wal_bytes_prev, 'index wal_bytes has increased');
+ok($index_wal_fpi > $index_wal_fpi_prev, 'index wal_fpi has increased');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -421,7 +554,9 @@ $node->safe_psql($dbname, "VACUUM vestat;");
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => 0,
- tab_wal_records => $wal_records_prev
+ tab_wal_records => $wal_records_prev,
+ idx_tuples_deleted => 0,
+ idx_wal_records => 0,
);
ok($updated, 'vacuum stats updated after updating tuples and trancation in the table (tuples_deleted and wal_records advanced)')
@@ -442,6 +577,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted == $index_tuples_deleted_prev, 'index tuples_deleted stay the same');
+ok($index_wal_records == $index_wal_records_prev, 'index wal_records stay the same');
+ok($index_wal_bytes == $index_wal_bytes_prev, 'index wal_bytes stay the same');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -464,7 +605,9 @@ $node->safe_psql(
$updated = wait_for_vacuum_stats(
tab_tuples_deleted => 0,
- tab_wal_records => $wal_records
+ tab_wal_records => $wal_records,
+ idx_tuples_deleted => 0,
+ idx_wal_records => 0,
);
ok($updated, 'vacuum stats updated after deleting all tuples and trancation in the table (tuples_deleted and wal_records advanced)')
@@ -485,6 +628,12 @@ ok($wal_records > $wal_records_prev, 'table wal_records has increased');
ok($wal_bytes > $wal_bytes_prev, 'table wal_bytes has increased');
ok($wal_fpi == $wal_fpi_prev, 'table wal_fpi stay the same');
+ok($index_pages_deleted == $index_pages_deleted_prev, 'index pages_deleted stay the same');
+ok($index_tuples_deleted == $index_tuples_deleted_prev, 'index tuples_deleted stay the same');
+ok($index_wal_records == $index_wal_records_prev, 'index wal_records stay the same');
+ok($index_wal_bytes == $index_wal_bytes_prev, 'index wal_bytes stay the same');
+ok($index_wal_fpi == $index_wal_fpi_prev, 'index wal_fpi stay the same');
+
} or print_vacuum_stats_on_error(); # End of subtest
# Save statistics for the next test
@@ -513,6 +662,34 @@ $base_stats = $node->safe_psql(
);
ok($base_stats eq 't', 'heap vacuum stats return from the current relation and database as expected');
+$reloid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'vestat_pkey';
+ }
+);
+
+# Check if we can get vacuum statistics of particular index relation in the current database
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 1 FROM pg_stat_vacuum_indexes($dboid, $reloid);"
+);
+ok($base_stats eq 't', 'index vacuum stats return from the current relation and database as expected');
+
+# Check if we return empty results if vacuum statistics with particular oid doesn't exist
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 0 FROM pg_stats_vacuum_tables($dboid, 1);"
+);
+ok($base_stats eq 't', 'table vacuum stats return no rows, as expected');
+
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT count(*) = 0 FROM pg_stat_vacuum_indexes($dboid, 1);"
+);
+ok($base_stats eq 't', 'index vacuum stats return no rows, as expected');
+
+
#------------------------------------------------------------------------------
# Test 9: Check relation-level vacuum statistics from another database
#------------------------------------------------------------------------------
@@ -523,6 +700,14 @@ $base_stats = $node->safe_psql(
FROM pg_stat_vacuum_tables
WHERE relname = 'vestat';"
);
+ok($base_stats eq 't', 'check the printing table vacuum extended statistics from another database are not available');
+
+$base_stats = $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) = 0
+ FROM pg_stat_vacuum_indexes
+ WHERE relname = 'vestat_pkey';"
+);
ok($base_stats eq 't', 'check the printing heap vacuum extended statistics from another database are not available');
$reloid = $node->safe_psql(
@@ -543,6 +728,23 @@ $base_stats = $node->safe_psql(
is($base_stats, 't', 'vacuum stats for common heap objects available');
+my $indoid = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT oid FROM pg_class WHERE relname = 'pg_shdepend_reference_index';
+ }
+);
+
+$base_stats = $node->safe_psql(
+ $dbname,
+ qq{
+ SELECT count(*) = 1
+ FROM pg_stat_vacuum_indexes(0, $indoid);
+ }
+);
+
+is($base_stats, 't', 'vacuum stats for common index objects available');
+
#------------------------------------------------------------------------------
# Test 11: Cleanup checks: ensure functions return empty sets for OID = 0
#------------------------------------------------------------------------------
@@ -562,6 +764,15 @@ $base_stats = $node->safe_psql(
);
ok($base_stats eq 't', 'pg_stat_vacuum_tables correctly returns no rows for OID = 0');
+$base_stats = $node->safe_psql(
+ $dbname,
+ q{
+ SELECT COUNT(*) = 0
+ FROM pg_stat_vacuum_indexes WHERE relid = 0;
+ }
+);
+ok($base_stats eq 't', 'pg_stat_vacuum_indexes correctly returns no rows for OID = 0');
+
$node->safe_psql('postgres',
"DROP DATABASE $dbname;"
);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e4a77878beb..7e6029394cb 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,6 +2330,28 @@ pg_stat_user_tables| SELECT relid,
rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
+ ns.nspname AS schemaname,
+ rel.relname,
+ stats.total_blks_read,
+ stats.total_blks_hit,
+ stats.total_blks_dirtied,
+ stats.total_blks_written,
+ stats.rel_blks_read,
+ stats.rel_blks_hit,
+ stats.pages_deleted,
+ stats.tuples_deleted,
+ stats.wal_records,
+ stats.wal_fpi,
+ stats.wal_bytes,
+ stats.blk_read_time,
+ stats.blk_write_time,
+ stats.delay_time,
+ stats.total_time
+ FROM (pg_class rel
+ JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
+ LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (rel.relkind = 'i'::"char");
pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
rel.relname,
stats.relid,
--
2.39.5 (Apple Git-154)
[text/plain] v26-0003-Machinery-for-grabbing-an-extended-vacuum-statistics.patch (22.4K, 5-v26-0003-Machinery-for-grabbing-an-extended-vacuum-statistics.patch)
download | inline diff:
From 2051db2600566dc88040cee97868469aa35440d7 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Mon, 1 Sep 2025 21:43:33 +0300
Subject: [PATCH 3/5] Machinery for grabbing an extended vacuum statistics on
databases.
Database vacuum statistics information is the collected general
vacuum statistics indexes and tables owned by the databases, which
they belong to.
In addition to the fact that there are far fewer databases in a system
than relations, vacuum statistics for a database contain fewer statistics
than relations, but they are enough to indicate that something may be
wrong in the system and prompt the administrator to enable extended
monitoring for relations.
So, buffer, wal, statistics of I/O time of read and writen blocks
statistics will be observed because they are collected for both
tables, indexes. In addition, we show the number of errors caught
during operation of the vacuum only for the error level.
wraparound_failsafe_count is a number of times when the vacuum starts
urgent cleanup to prevent wraparound problem which is critical for
the database.
Authors: Alena Rybakina <[email protected]>,
Andrei Lepikhov <[email protected]>,
Andrei Zubkov <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>, Masahiko Sawada <[email protected]>,
Ilia Evdokimov <[email protected]>, jian he <[email protected]>,
Kirill Reshke <[email protected]>, Alexander Korotkov <[email protected]>,
Jim Nasby <[email protected]>, Sami Imseih <[email protected]>
---
src/backend/access/heap/vacuumlazy.c | 2 +-
src/backend/catalog/system_views.sql | 26 +++++++-
src/backend/utils/activity/pgstat_database.c | 1 +
src/backend/utils/activity/pgstat_relation.c | 13 +++-
src/backend/utils/adt/pgstatfuncs.c | 62 ++++++++++++++++++-
src/include/catalog/pg_proc.dat | 13 +++-
src/include/pgstat.h | 7 +--
.../vacuum-extending-in-repetable-read.spec | 6 ++
.../t/050_vacuum_extending_basic_test.pl | 49 ++++++++++++---
.../t/051_vacuum_extending_freeze_test.pl | 48 +++++---------
src/test/regress/expected/rules.out | 17 +++++
11 files changed, 195 insertions(+), 49 deletions(-)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 719ce90d96d..fcd92a43dda 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -663,7 +663,7 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats
extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
- extVacStats->table.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+ extVacStats->wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 47b6a00d297..dc86b1ee212 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1533,4 +1533,28 @@ FROM
pg_class rel
JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
-WHERE rel.relkind = 'i';
\ No newline at end of file
+WHERE rel.relkind = 'i';
+
+CREATE VIEW pg_stat_vacuum_database AS
+SELECT
+ db.oid as dboid,
+ db.datname AS dbname,
+
+ stats.db_blks_read AS db_blks_read,
+ stats.db_blks_hit AS db_blks_hit,
+ stats.total_blks_dirtied AS total_blks_dirtied,
+ stats.total_blks_written AS total_blks_written,
+
+ stats.wal_records AS wal_records,
+ stats.wal_fpi AS wal_fpi,
+ stats.wal_bytes AS wal_bytes,
+
+ stats.blk_read_time AS blk_read_time,
+ stats.blk_write_time AS blk_write_time,
+
+ stats.delay_time AS delay_time,
+ stats.total_time AS total_time,
+ stats.wraparound_failsafe AS wraparound_failsafe
+FROM
+ pg_database db,
+ LATERAL pg_stat_get_vacuum_database(db.oid) stats;
\ No newline at end of file
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index b31f20d41bc..65207d30378 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -485,6 +485,7 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
pgstat_unlock_entry(entry_ref);
memset(pendingent, 0, sizeof(*pendingent));
+ memset(&(pendingent)->vacuum_ext, 0, sizeof(ExtVacReport));
return true;
}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 4bd6afc3794..2675c541369 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -215,6 +215,7 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
PgStat_EntryRef *entry_ref;
PgStatShared_Relation *shtabentry;
PgStat_StatTabEntry *tabentry;
+ PgStatShared_Database *dbentry;
Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
TimestampTz ts;
PgStat_Counter elapsedtime;
@@ -273,6 +274,16 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
*/
pgstat_flush_io(false);
(void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
+
+ if (dboid != InvalidOid)
+ {
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
+ dboid, InvalidOid, false);
+ dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
+
+ pgstat_accumulate_extvac_stats(&dbentry->stats.vacuum_ext, params, false);
+ pgstat_unlock_entry(entry_ref);
+ }
}
/*
@@ -1032,6 +1043,7 @@ pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
dst->blk_write_time += src->blk_write_time;
dst->delay_time += src->delay_time;
dst->total_time += src->total_time;
+ dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
if (!accumulate_reltype_specific_info)
return;
@@ -1059,7 +1071,6 @@ pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
dst->table.index_vacuum_count += src->table.index_vacuum_count;
dst->table.missed_dead_pages += src->table.missed_dead_pages;
dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
- dst->table.wraparound_failsafe_count += src->table.wraparound_failsafe_count;
}
else if (dst->type == PGSTAT_EXTVAC_INDEX)
{
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 755751c3b46..4e2714f2e6a 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2371,7 +2371,7 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
- values[i++] = Int32GetDatum(extvacuum->table.wraparound_failsafe_count);
+ values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
values[i++] = Int64GetDatum(extvacuum->wal_records);
@@ -2463,3 +2463,63 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 14
+
+ Oid dbid = PG_GETARG_OID(0);
+ PgStat_StatDBEntry *dbentry;
+ ExtVacReport *extvacuum;
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
+ char buf[256];
+ int i = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ dbentry = pgstat_fetch_stat_dbentry(dbid);
+
+ if (dbentry == NULL)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
+ else
+ {
+ extvacuum = &(dbentry->vacuum_ext);
+ }
+
+ i = 0;
+
+ values[i++] = ObjectIdGetDatum(dbid);
+
+ values[i++] = Int64GetDatum(extvacuum->total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+
+ values[i++] = Int64GetDatum(extvacuum->wal_records);
+ values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+
+ /* Convert to numeric, like pg_stat_statements */
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ values[i++] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ values[i++] = Float8GetDatum(extvacuum->blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->delay_time);
+ values[i++] = Float8GetDatum(extvacuum->total_time);
+ values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+
+ Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
+
+ /* Returns the record as Datum */
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e957781b623..c3a2adb96f1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12631,12 +12631,21 @@
proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
prosrc => 'pg_stat_get_rev_all_frozen_pages' },
{ oid => '8004',
- descr => 'pg_stat_get_vacuum_indexes return stats values',
+ descr => 'pg_stat_get_vacuum_indexes returns vacuum stats values for index',
proname => 'pg_stat_get_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
proretset => 't',
proargtypes => 'oid',
proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8}',
proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
proargnames => '{reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_deleted,tuples_deleted,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time}',
- prosrc => 'pg_stat_get_vacuum_indexes' }
+ prosrc => 'pg_stat_get_vacuum_indexes' },
+{ oid => '8005',
+ descr => 'pg_stat_get_vacuum_database returns vacuum stats values for database',
+ proname => 'pg_stat_get_vacuum_database', prorows => 1000, provolatile => 's', prorettype => 'record',proisstrict => 'f',
+ proretset => 't',
+ proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,int4}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{dbid,dboid,db_blks_read,db_blks_hit,total_blks_dirtied,total_blks_written,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,total_time,wraparound_failsafe}',
+ prosrc => 'pg_stat_get_vacuum_database' },
]
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f2881dbb6f9..f3bdc1c38df 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -165,6 +165,9 @@ typedef struct ExtVacReport
int64 tuples_deleted; /* tuples deleted by vacuum */
+ int32 wraparound_failsafe_count; /* the number of times to prevent
+ * wraparound problem */
+
ExtVacReportType type; /* heap, index, etc. */
/* ----------
@@ -205,10 +208,6 @@ typedef struct ExtVacReport
* lock */
int64 missed_dead_pages; /* pages with missed dead tuples */
int64 index_vacuum_count; /* number of index vacuumings */
- int32 wraparound_failsafe_count; /* number of emergency
- * vacuums to prevent
- * anti-wraparound
- * shutdown */
} table;
struct
{
diff --git a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
index 5893d89573d..cfec3159580 100644
--- a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
+++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec
@@ -18,6 +18,9 @@ teardown
}
session s1
+setup {
+ SET track_vacuum_statistics TO 'on';
+ }
step s1_begin_repeatable_read {
BEGIN transaction ISOLATION LEVEL REPEATABLE READ;
select count(ival) from test_vacuum_stat_isolation where id>900;
@@ -25,6 +28,9 @@ step s1_begin_repeatable_read {
step s1_commit { COMMIT; }
session s2
+setup {
+ SET track_vacuum_statistics TO 'on';
+ }
step s2_insert { INSERT INTO test_vacuum_stat_isolation(id, ival) SELECT ival, ival%10 FROM generate_series(1,1000) As ival; }
step s2_update { UPDATE test_vacuum_stat_isolation SET ival = ival + 2 where id > 900; }
step s2_delete { DELETE FROM test_vacuum_stat_isolation where id > 900; }
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index 8f7b1e2909b..bd3cb544e30 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -2,10 +2,11 @@
# Test cumulative vacuum stats system using TAP
#
# This test validates the accuracy and behavior of cumulative vacuum statistics
-# across heap tables, indexes using:
+# across heap tables, indexes, and databases using:
#
# • pg_stat_vacuum_tables
# • pg_stat_vacuum_indexes
+# • pg_stat_vacuum_database
#
# A polling helper function repeatedly checks the stats views until expected
# deltas appear or a configurable timeout expires. This guarantees that
@@ -672,20 +673,20 @@ $reloid = $node->safe_psql(
# Check if we can get vacuum statistics of particular index relation in the current database
$base_stats = $node->safe_psql(
$dbname,
- "SELECT count(*) = 1 FROM pg_stat_vacuum_indexes($dboid, $reloid);"
+ "SELECT count(*) = 1 FROM pg_stat_get_vacuum_indexes($reloid);"
);
ok($base_stats eq 't', 'index vacuum stats return from the current relation and database as expected');
# Check if we return empty results if vacuum statistics with particular oid doesn't exist
$base_stats = $node->safe_psql(
$dbname,
- "SELECT count(*) = 0 FROM pg_stats_vacuum_tables($dboid, 1);"
+ "SELECT count(*) = 0 FROM pg_stat_get_vacuum_tables(1);"
);
ok($base_stats eq 't', 'table vacuum stats return no rows, as expected');
$base_stats = $node->safe_psql(
$dbname,
- "SELECT count(*) = 0 FROM pg_stat_vacuum_indexes($dboid, 1);"
+ "SELECT count(*) = 0 FROM pg_stat_get_vacuum_indexes(1);"
);
ok($base_stats eq 't', 'index vacuum stats return no rows, as expected');
@@ -708,7 +709,31 @@ $base_stats = $node->safe_psql(
FROM pg_stat_vacuum_indexes
WHERE relname = 'vestat_pkey';"
);
-ok($base_stats eq 't', 'check the printing heap vacuum extended statistics from another database are not available');
+ok($base_stats eq 't', 'check the printing index vacuum extended statistics from another database are not available');
+
+#--------------------------------------------------------------------------------------
+# Test 10: Check database-level vacuum statistics from the current and another database
+#--------------------------------------------------------------------------------------
+
+$base_stats = $node->safe_psql(
+ $dbname,
+ "SELECT db_blks_hit > 0 AND total_blks_dirtied > 0
+ AND total_blks_written > 0 AND wal_records > 0
+ AND wal_fpi > 0 AND wal_bytes > 0
+ FROM pg_stat_vacuum_database, pg_database
+ WHERE pg_database.datname = '$dbname'
+ AND pg_database.oid = pg_stat_vacuum_database.dboid;"
+);
+ok($base_stats eq 't', 'check database-level vacuum stats from the current database are available');
+
+$base_stats = $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) > 0
+ FROM pg_stat_vacuum_database, pg_database
+ WHERE pg_database.datname = '$dbname'
+ AND pg_database.oid = pg_stat_vacuum_database.dboid;"
+);
+ok($base_stats eq 't', 'check database-level vacuum stats from another database are available');
$reloid = $node->safe_psql(
$dbname,
@@ -739,7 +764,7 @@ $base_stats = $node->safe_psql(
$dbname,
qq{
SELECT count(*) = 1
- FROM pg_stat_vacuum_indexes(0, $indoid);
+ FROM pg_stat_get_vacuum_indexes($indoid);
}
);
@@ -773,8 +798,18 @@ $base_stats = $node->safe_psql(
);
ok($base_stats eq 't', 'pg_stat_vacuum_indexes correctly returns no rows for OID = 0');
+$base_stats = $node->safe_psql(
+ 'postgres',
+ q{
+ SELECT COUNT(*) = 0
+ FROM pg_stat_vacuum_database WHERE dboid = 0;
+ }
+);
+ok($base_stats eq 't', 'pg_stat_vacuum_database correctly returns no rows for OID = 0');
+
$node->safe_psql('postgres',
- "DROP DATABASE $dbname;"
+ "DROP DATABASE $dbname;
+ VACUUM;"
);
$node->stop;
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
index a9b5d6cb739..7528f20098b 100644
--- a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -91,11 +91,17 @@ sub wait_for_vacuum_stats {
my $start = time();
my $sql;
+ my $vacuum_run = 0;
+
+ # Run VACUUM once if requested, before polling
+ if ($run_vacuum) {
+ $node->safe_psql($dbname, 'VACUUM (FREEZE, VERBOSE) vestat');
+ $vacuum_run = 1;
+ }
while ((time() - $start) < $timeout) {
if ($run_vacuum) {
- $node->safe_psql($dbname, 'VACUUM (FREEZE, VERBOSE) vestat');
$sql = "
SELECT ($tab_frozen_column > $tab_all_frozen_pages_count AND
$tab_visible_column > $tab_all_visible_pages_count)
@@ -213,20 +219,6 @@ $node->safe_psql($dbname, q{
VACUUM (FREEZE, VERBOSE) vestat;
});
-# Poll the stats view until the expected deltas appear or timeout.
-# We do not expect rev_all_* counters to change here, so we pass -1 for them.
-$updated = wait_for_vacuum_stats(
- tab_frozen_column => 'vm_new_frozen_pages',
- tab_visible_column => 'vm_new_visible_pages',
- tab_all_frozen_pages_count => 0,
- tab_all_visible_pages_count => 0,
- run_vacuum => 1,
-);
-
-ok($updated,
- 'vacuum stats updated after vacuuming the table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
- or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
-
#------------------------------------------------------------------------------
# Snapshot current statistics for later comparison
#------------------------------------------------------------------------------
@@ -238,7 +230,7 @@ fetch_vacuum_stats();
#------------------------------------------------------------------------------
$res = $node->safe_psql($dbname, q{
- SELECT vm_new_frozen_pages > 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
+ SELECT vm_new_frozen_pages = 0 FROM pg_stat_vacuum_tables WHERE relname = 'vestat';
});
ok($res eq 't', 'vacuum froze some pages, as expected') or
fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
@@ -335,32 +327,24 @@ fetch_vacuum_stats();
$node->safe_psql($dbname, q{ VACUUM (FREEZE, VERBOSE) vestat; });
-# Poll until stats update or timeout.
-# We pass current snapshot values for vm_new_frozen_pages/vm_new_visible_pages and expect rev counters unchanged.
-$updated = wait_for_vacuum_stats(
- tab_frozen_column => 'vm_new_frozen_pages',
- tab_visible_column => 'vm_new_visible_pages',
- tab_all_frozen_pages_count => $vm_new_frozen_pages,
- tab_all_visible_pages_count => $vm_new_visible_pages,
- run_vacuum => 1,
-);
-
-ok($updated,
- 'vacuum stats updated after vacuuming the all-updated table (vm_new_frozen_pages and vm_new_visible_pages advanced)')
- or diag "Timeout waiting for pg_stat_vacuum_tables to update after $timeout seconds during vacuum";
-
#------------------------------------------------------------------------------
# Verify statistics after final vacuum
# Check updated stats after backend work
#------------------------------------------------------------------------------
+
+# Fetch updated statistics to get the new baseline for comparison
+my $old_vm_new_frozen_pages = $vm_new_frozen_pages;
+my $old_vm_new_visible_pages = $vm_new_visible_pages;
+fetch_vacuum_stats();
+
$res = $node->safe_psql($dbname,
- "SELECT vm_new_frozen_pages > $vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+ "SELECT vm_new_frozen_pages = $old_vm_new_frozen_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
);
ok($res eq 't', 'vacuum froze some pages after backend activity, as expected') or
fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_frozen_pages', tab_value => $vm_new_frozen_pages,);
$res = $node->safe_psql($dbname,
- "SELECT vm_new_visible_pages > $vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
+ "SELECT vm_new_visible_pages > $old_vm_new_visible_pages FROM pg_stat_vacuum_tables WHERE relname = 'vestat';"
);
ok($res eq 't', 'vacuum marked pages all-visible after backend activity, as expected') or
fetch_error_tab_vacuum_statistics(tab_column => 'vm_new_visible_pages', tab_value => $vm_new_visible_pages,);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7e6029394cb..b627c85e332 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,6 +2330,23 @@ pg_stat_user_tables| SELECT relid,
rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_database| SELECT db.oid AS dboid,
+ db.datname AS dbname,
+ stats.db_blks_read,
+ stats.db_blks_hit,
+ stats.total_blks_dirtied,
+ stats.total_blks_written,
+ stats.wal_records,
+ stats.wal_fpi,
+ stats.wal_bytes,
+ stats.blk_read_time,
+ stats.blk_write_time,
+ stats.delay_time,
+ stats.total_time,
+ stats.wraparound_failsafe,
+ stats.errors
+ FROM pg_database db,
+ LATERAL pg_stat_get_vacuum_database(db.oid) stats(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
ns.nspname AS schemaname,
rel.relname,
--
2.39.5 (Apple Git-154)
[text/plain] v26-0004-Vacuum-statistics-have-been-separated-from-regular.patch (71.3K, 6-v26-0004-Vacuum-statistics-have-been-separated-from-regular.patch)
download | inline diff:
From 12f4596c25e15d1f7566b87334615ad112cfb64e Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Sun, 21 Dec 2025 01:40:06 +0300
Subject: [PATCH 4/5] Vacuum statistics have been separated from regular
relation and database statistics to reduce memory usage. Dedicated
PGSTAT_KIND_VACUUM_RELATION and PGSTAT_KIND_VACUUM_DB entries were added to
the stats collector to efficiently allocate memory for vacuum-specific
metrics, which require significantly more space per relation.
---
src/backend/access/heap/vacuumlazy.c | 124 +++++-----
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 1 +
src/backend/catalog/system_views.sql | 177 ++++++++-------
src/backend/commands/dbcommands.c | 1 +
src/backend/commands/vacuumparallel.c | 5 +-
src/backend/utils/activity/Makefile | 1 +
src/backend/utils/activity/pgstat.c | 30 ++-
src/backend/utils/activity/pgstat_database.c | 10 +-
src/backend/utils/activity/pgstat_relation.c | 75 +-----
src/backend/utils/activity/pgstat_vacuum.c | 214 ++++++++++++++++++
src/backend/utils/adt/pgstatfuncs.c | 149 ++++++------
src/backend/utils/misc/guc_parameters.dat | 6 +
src/include/commands/vacuum.h | 2 +-
src/include/pgstat.h | 208 +++++++++--------
src/include/utils/pgstat_internal.h | 15 ++
src/include/utils/pgstat_kind.h | 4 +-
.../t/050_vacuum_extending_basic_test.pl | 21 +-
.../t/051_vacuum_extending_freeze_test.pl | 1 +
src/test/regress/expected/rules.out | 146 ++++++------
20 files changed, 735 insertions(+), 456 deletions(-)
create mode 100644 src/backend/utils/activity/pgstat_vacuum.c
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index fcd92a43dda..3f1ed040908 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -413,7 +413,8 @@ typedef struct LVRelState
int32 wraparound_failsafe_count; /* number of emergency vacuums to
* prevent anti-wraparound
* shutdown */
- ExtVacReport extVacReportIdx;
+
+ PgStat_VacuumRelationCounts extVacReportIdx;
} LVRelState;
@@ -505,6 +506,9 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
{
TimestampTz starttime;
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
memset(counters, 0, sizeof(LVExtStatCounters));
starttime = GetCurrentTimestamp();
@@ -536,7 +540,7 @@ extvac_stats_start(Relation rel, LVExtStatCounters * counters)
*/
static void
extvac_stats_end(Relation rel, LVExtStatCounters * counters,
- ExtVacReport * report)
+ PgStat_CommonCounts * report)
{
WalUsage walusage;
BufferUsage bufusage;
@@ -544,6 +548,11 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters,
long secs;
int usecs;
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ memset(report, 0, sizeof(PgStat_CommonCounts));
+
/* Calculate diffs of global stat parameters on WAL and buffer usage. */
memset(&walusage, 0, sizeof(WalUsage));
WalUsageAccumDiff(&walusage, &pgWalUsage, &counters->walusage);
@@ -592,6 +601,9 @@ void
extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
LVExtStatCountersIdx * counters)
{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
/* Set initial values for common heap and index statistics */
extvac_stats_start(rel, &counters->common);
counters->pages_deleted = counters->tuples_removed = 0;
@@ -609,11 +621,15 @@ extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
void
extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
- LVExtStatCountersIdx * counters, ExtVacReport * report)
+ LVExtStatCountersIdx * counters, PgStat_VacuumRelationCounts * report)
{
- memset(report, 0, sizeof(ExtVacReport));
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ memset(report, 0, sizeof(PgStat_VacuumRelationCounts));
+
+ extvac_stats_end(rel, &counters->common, &report->common);
- extvac_stats_end(rel, &counters->common, report);
report->type = PGSTAT_EXTVAC_INDEX;
if (stats != NULL)
@@ -624,7 +640,7 @@ extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
*/
/* Fill index-specific extended stats fields */
- report->tuples_deleted =
+ report->common.tuples_deleted =
stats->tuples_removed - counters->tuples_removed;
report->index.pages_deleted =
stats->pages_deleted - counters->pages_deleted;
@@ -647,8 +663,11 @@ extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
* procudure.
*/
static void
-accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats)
+accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacStats)
{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
/* Fill heap-specific extended stats fields */
extVacStats->type = PGSTAT_EXTVAC_TABLE;
extVacStats->table.pages_scanned = vacrel->scanned_pages;
@@ -656,46 +675,45 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacStats
extVacStats->table.vm_new_frozen_pages = vacrel->vm_new_frozen_pages;
extVacStats->table.vm_new_visible_pages = vacrel->vm_new_visible_pages;
extVacStats->table.vm_new_visible_frozen_pages = vacrel->vm_new_visible_frozen_pages;
- extVacStats->tuples_deleted = vacrel->tuples_deleted;
+ extVacStats->common.tuples_deleted = vacrel->tuples_deleted;
extVacStats->table.tuples_frozen = vacrel->tuples_frozen;
extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
extVacStats->table.recently_dead_tuples = vacrel->recently_dead_tuples;
extVacStats->table.missed_dead_tuples = vacrel->missed_dead_tuples;
extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages;
extVacStats->table.index_vacuum_count = vacrel->num_index_scans;
- extVacStats->wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
+ extVacStats->common.wraparound_failsafe_count = vacrel->wraparound_failsafe_count;
- extVacStats->blk_read_time -= vacrel->extVacReportIdx.blk_read_time;
- extVacStats->blk_write_time -= vacrel->extVacReportIdx.blk_write_time;
- extVacStats->total_blks_dirtied -= vacrel->extVacReportIdx.total_blks_dirtied;
- extVacStats->total_blks_hit -= vacrel->extVacReportIdx.total_blks_hit;
- extVacStats->total_blks_read -= vacrel->extVacReportIdx.total_blks_read;
- extVacStats->total_blks_written -= vacrel->extVacReportIdx.total_blks_written;
- extVacStats->wal_bytes -= vacrel->extVacReportIdx.wal_bytes;
- extVacStats->wal_fpi -= vacrel->extVacReportIdx.wal_fpi;
- extVacStats->wal_records -= vacrel->extVacReportIdx.wal_records;
+ extVacStats->common.blk_read_time -= vacrel->extVacReportIdx.common.blk_read_time;
+ extVacStats->common.blk_write_time -= vacrel->extVacReportIdx.common.blk_write_time;
+ extVacStats->common.total_blks_dirtied -= vacrel->extVacReportIdx.common.total_blks_dirtied;
+ extVacStats->common.total_blks_hit -= vacrel->extVacReportIdx.common.total_blks_hit;
+ extVacStats->common.total_blks_read -= vacrel->extVacReportIdx.common.total_blks_read;
+ extVacStats->common.total_blks_written -= vacrel->extVacReportIdx.common.total_blks_written;
+ extVacStats->common.wal_bytes -= vacrel->extVacReportIdx.common.wal_bytes;
+ extVacStats->common.wal_fpi -= vacrel->extVacReportIdx.common.wal_fpi;
+ extVacStats->common.wal_records -= vacrel->extVacReportIdx.common.wal_records;
- extVacStats->total_time -= vacrel->extVacReportIdx.total_time;
- extVacStats->delay_time -= vacrel->extVacReportIdx.delay_time;
+ extVacStats->common.total_time -= vacrel->extVacReportIdx.common.total_time;
+ extVacStats->common.delay_time -= vacrel->extVacReportIdx.common.delay_time;
}
static void
-accumulate_idxs_vacuum_statistics(LVRelState *vacrel, ExtVacReport * extVacIdxStats)
+accumulate_idxs_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacIdxStats)
{
/* Fill heap-specific extended stats fields */
- vacrel->extVacReportIdx.blk_read_time += extVacIdxStats->blk_read_time;
- vacrel->extVacReportIdx.blk_write_time += extVacIdxStats->blk_write_time;
- vacrel->extVacReportIdx.total_blks_dirtied += extVacIdxStats->total_blks_dirtied;
- vacrel->extVacReportIdx.total_blks_hit += extVacIdxStats->total_blks_hit;
- vacrel->extVacReportIdx.total_blks_read += extVacIdxStats->total_blks_read;
- vacrel->extVacReportIdx.total_blks_written += extVacIdxStats->total_blks_written;
- vacrel->extVacReportIdx.wal_bytes += extVacIdxStats->wal_bytes;
- vacrel->extVacReportIdx.wal_fpi += extVacIdxStats->wal_fpi;
- vacrel->extVacReportIdx.wal_records += extVacIdxStats->wal_records;
- vacrel->extVacReportIdx.delay_time += extVacIdxStats->delay_time;
-
- vacrel->extVacReportIdx.total_time += extVacIdxStats->total_time;
+ vacrel->extVacReportIdx.common.blk_read_time += extVacIdxStats->common.blk_read_time;
+ vacrel->extVacReportIdx.common.blk_write_time += extVacIdxStats->common.blk_write_time;
+ vacrel->extVacReportIdx.common.total_blks_dirtied += extVacIdxStats->common.total_blks_dirtied;
+ vacrel->extVacReportIdx.common.total_blks_hit += extVacIdxStats->common.total_blks_hit;
+ vacrel->extVacReportIdx.common.total_blks_read += extVacIdxStats->common.total_blks_read;
+ vacrel->extVacReportIdx.common.total_blks_written += extVacIdxStats->common.total_blks_written;
+ vacrel->extVacReportIdx.common.wal_bytes += extVacIdxStats->common.wal_bytes;
+ vacrel->extVacReportIdx.common.wal_fpi += extVacIdxStats->common.wal_fpi;
+ vacrel->extVacReportIdx.common.wal_records += extVacIdxStats->common.wal_records;
+ vacrel->extVacReportIdx.common.delay_time += extVacIdxStats->common.delay_time;
+ vacrel->extVacReportIdx.common.total_time += extVacIdxStats->common.total_time;
}
@@ -856,10 +874,10 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
ErrorContextCallback errcallback;
char **indnames = NULL;
LVExtStatCounters extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
/* Initialize vacuum statistics */
- memset(&extVacReport, 0, sizeof(ExtVacReport));
+ memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts));
verbose = (params.options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -915,7 +933,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
- memset(&vacrel->extVacReportIdx, 0, sizeof(ExtVacReport));
+ memset(&vacrel->extVacReportIdx, 0, sizeof(PgStat_VacuumRelationCounts));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
/* Set up high level stuff about rel and its indexes */
vacrel->rel = rel;
@@ -1173,7 +1192,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
&frozenxid_updated, &minmulti_updated, false);
/* Make generic extended vacuum stats report */
- extvac_stats_end(rel, &extVacCounters, &extVacReport);
+ /* extvac_stats_end(rel, &extVacCounters, &extVacReport.common); */
/*
* Report results to the cumulative stats system, too.
@@ -1190,14 +1209,14 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
* Make generic extended vacuum stats report and fill heap-specific
* extended stats fields.
*/
- extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport);
+ extvac_stats_end(vacrel->rel, &extVacCounters, &extVacReport.common);
accumulate_heap_vacuum_statistics(vacrel, &extVacReport);
+ pgstat_report_vacuum_extstats(vacrel->reloid, rel->rd_rel->relisshared, &extVacReport);
pgstat_report_vacuum(rel,
Max(vacrel->new_live_tuples, 0),
vacrel->recently_dead_tuples +
vacrel->missed_dead_tuples,
- starttime,
- &extVacReport);
+ starttime);
pgstat_progress_end_command();
if (instrument)
@@ -2902,9 +2921,9 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
else
{
LVExtStatCounters counters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
- memset(&extVacReport, 0, sizeof(ExtVacReport));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
extvac_stats_start(vacrel->rel, &counters);
@@ -2912,7 +2931,7 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples,
vacrel->num_index_scans);
- extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport.common);
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
/*
@@ -3345,9 +3364,9 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
else
{
LVExtStatCounters counters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
- memset(&extVacReport, 0, sizeof(ExtVacReport));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
extvac_stats_start(vacrel->rel, &counters);
@@ -3356,7 +3375,7 @@ lazy_cleanup_all_indexes(LVRelState *vacrel)
vacrel->num_index_scans,
estimated_count);
- extvac_stats_end(vacrel->rel, &counters, &extVacReport);
+ extvac_stats_end(vacrel->rel, &counters, &extVacReport.common);
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
}
@@ -3384,7 +3403,10 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
LVExtStatCountersIdx extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
+
+ memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts));
+ memset(&extVacReport.common, 0, sizeof(PgStat_CommonCounts));
/* Set initial statistics values to gather vacuum statistics for the index */
extvac_stats_start_idx(indrel, istat, &extVacCounters);
@@ -3421,8 +3443,7 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
if (!ParallelVacuumIsActive(vacrel))
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
- pgstat_report_vacuum(indrel,
- 0, 0, 0, &extVacReport);
+ pgstat_report_vacuum_extstats(vacrel->indoid, indrel->rd_rel->relisshared, &extVacReport);
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
@@ -3449,7 +3470,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
IndexVacuumInfo ivinfo;
LVSavedErrInfo saved_err_info;
LVExtStatCountersIdx extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
/* Set initial statistics values to gather vacuum statistics for the index */
extvac_stats_start_idx(indrel, istat, &extVacCounters);
@@ -3485,8 +3506,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
if (!ParallelVacuumIsActive(vacrel))
accumulate_idxs_vacuum_statistics(vacrel, &extVacReport);
- pgstat_report_vacuum(indrel,
- 0, 0, 0, &extVacReport);
+ pgstat_report_vacuum_extstats(RelationGetRelid(indrel), indrel->rd_rel->relisshared, &extVacReport);
/* Revert to the previous phase information for error traceback */
restore_vacuum_error_info(vacrel, &saved_err_info);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 265cc3e5fbf..680b76b8ef9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1883,6 +1883,7 @@ heap_drop_with_catalog(Oid relid)
/* ensure that stats are dropped if transaction commits */
pgstat_drop_relation(rel);
+ pgstat_vacuum_relation_delete_pending_cb(RelationGetRelid(rel));
/*
* Close relcache entry, but *keep* AccessExclusiveLock on the relation
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8dea58ad96b..e906f9e1856 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2327,6 +2327,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
/* ensure that stats are dropped if transaction commits */
pgstat_drop_relation(userIndexRelation);
+ pgstat_vacuum_relation_delete_pending_cb(RelationGetRelid(userIndexRelation));
/*
* Close and flush the index's relcache entry, to ensure relcache doesn't
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index dc86b1ee212..4ec2a7d9f10 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1462,99 +1462,104 @@ GRANT EXECUTE ON FUNCTION pg_get_aios() TO pg_read_all_stats;
--
CREATE VIEW pg_stat_vacuum_tables AS
-SELECT
- ns.nspname AS schemaname,
- rel.relname AS relname,
- stats.relid as relid,
-
- stats.total_blks_read AS total_blks_read,
- stats.total_blks_hit AS total_blks_hit,
- stats.total_blks_dirtied AS total_blks_dirtied,
- stats.total_blks_written AS total_blks_written,
-
- stats.rel_blks_read AS rel_blks_read,
- stats.rel_blks_hit AS rel_blks_hit,
-
- stats.pages_scanned AS pages_scanned,
- stats.pages_removed AS pages_removed,
- stats.vm_new_frozen_pages AS vm_new_frozen_pages,
- stats.vm_new_visible_pages AS vm_new_visible_pages,
- stats.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
- stats.missed_dead_pages AS missed_dead_pages,
- stats.tuples_deleted AS tuples_deleted,
- stats.tuples_frozen AS tuples_frozen,
- stats.recently_dead_tuples AS recently_dead_tuples,
- stats.missed_dead_tuples AS missed_dead_tuples,
-
- stats.wraparound_failsafe AS wraparound_failsafe,
- stats.index_vacuum_count AS index_vacuum_count,
- stats.wal_records AS wal_records,
- stats.wal_fpi AS wal_fpi,
- stats.wal_bytes AS wal_bytes,
-
- stats.blk_read_time AS blk_read_time,
- stats.blk_write_time AS blk_write_time,
-
- stats.delay_time AS delay_time,
- stats.total_time AS total_time
-
-FROM pg_class rel
- JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
- LATERAL pg_stat_get_vacuum_tables(rel.oid) stats
-WHERE rel.relkind = 'r';
+ SELECT
+ N.nspname AS schemaname,
+ C.relname AS relname,
+ S.relid as relid,
+
+ S.total_blks_read AS total_blks_read,
+ S.total_blks_hit AS total_blks_hit,
+ S.total_blks_dirtied AS total_blks_dirtied,
+ S.total_blks_written AS total_blks_written,
+
+ S.rel_blks_read AS rel_blks_read,
+ S.rel_blks_hit AS rel_blks_hit,
+
+ S.pages_scanned AS pages_scanned,
+ S.pages_removed AS pages_removed,
+ S.vm_new_frozen_pages AS vm_new_frozen_pages,
+ S.vm_new_visible_pages AS vm_new_visible_pages,
+ S.vm_new_visible_frozen_pages AS vm_new_visible_frozen_pages,
+ S.missed_dead_pages AS missed_dead_pages,
+ S.tuples_deleted AS tuples_deleted,
+ S.tuples_frozen AS tuples_frozen,
+ S.recently_dead_tuples AS recently_dead_tuples,
+ S.missed_dead_tuples AS missed_dead_tuples,
+
+ S.wraparound_failsafe AS wraparound_failsafe,
+ S.index_vacuum_count AS index_vacuum_count,
+ S.wal_records AS wal_records,
+ S.wal_fpi AS wal_fpi,
+ S.wal_bytes AS wal_bytes,
+
+ S.blk_read_time AS blk_read_time,
+ S.blk_write_time AS blk_write_time,
+
+ S.delay_time AS delay_time,
+ S.total_time AS total_time
+
+ FROM pg_class C JOIN
+ pg_namespace N ON N.oid = C.relnamespace,
+ LATERAL pg_stat_get_vacuum_tables(C.oid) S
+ WHERE C.relkind IN ('r', 't', 'm');
CREATE VIEW pg_stat_vacuum_indexes AS
-SELECT
- rel.oid as relid,
- ns.nspname AS schemaname,
- rel.relname AS relname,
+ SELECT
+ C.oid AS relid,
+ I.oid AS indexrelid,
+ N.nspname AS schemaname,
+ C.relname AS relname,
+ I.relname AS indexrelname,
- total_blks_read AS total_blks_read,
- total_blks_hit AS total_blks_hit,
- total_blks_dirtied AS total_blks_dirtied,
- total_blks_written AS total_blks_written,
+ S.total_blks_read AS total_blks_read,
+ S.total_blks_hit AS total_blks_hit,
+ S.total_blks_dirtied AS total_blks_dirtied,
+ S.total_blks_written AS total_blks_written,
- rel_blks_read AS rel_blks_read,
- rel_blks_hit AS rel_blks_hit,
+ S.rel_blks_read AS rel_blks_read,
+ S.rel_blks_hit AS rel_blks_hit,
- pages_deleted AS pages_deleted,
- tuples_deleted AS tuples_deleted,
+ S.pages_deleted AS pages_deleted,
+ S.tuples_deleted AS tuples_deleted,
- wal_records AS wal_records,
- wal_fpi AS wal_fpi,
- wal_bytes AS wal_bytes,
+ S.wal_records AS wal_records,
+ S.wal_fpi AS wal_fpi,
+ S.wal_bytes AS wal_bytes,
- blk_read_time AS blk_read_time,
- blk_write_time AS blk_write_time,
+ S.blk_read_time AS blk_read_time,
+ S.blk_write_time AS blk_write_time,
- delay_time AS delay_time,
- total_time AS total_time
-FROM
- pg_class rel
- JOIN pg_namespace ns ON ns.oid = rel.relnamespace,
- LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats
-WHERE rel.relkind = 'i';
+ S.delay_time AS delay_time,
+ S.total_time AS total_time
+ FROM
+ pg_class C JOIN
+ pg_index X ON C.oid = X.indrelid JOIN
+ pg_class I ON I.oid = X.indexrelid
+ LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace),
+ LATERAL pg_stat_get_vacuum_indexes(I.oid) S
+ WHERE C.relkind IN ('r', 't', 'm');
CREATE VIEW pg_stat_vacuum_database AS
-SELECT
- db.oid as dboid,
- db.datname AS dbname,
-
- stats.db_blks_read AS db_blks_read,
- stats.db_blks_hit AS db_blks_hit,
- stats.total_blks_dirtied AS total_blks_dirtied,
- stats.total_blks_written AS total_blks_written,
-
- stats.wal_records AS wal_records,
- stats.wal_fpi AS wal_fpi,
- stats.wal_bytes AS wal_bytes,
-
- stats.blk_read_time AS blk_read_time,
- stats.blk_write_time AS blk_write_time,
-
- stats.delay_time AS delay_time,
- stats.total_time AS total_time,
- stats.wraparound_failsafe AS wraparound_failsafe
-FROM
- pg_database db,
- LATERAL pg_stat_get_vacuum_database(db.oid) stats;
\ No newline at end of file
+ SELECT
+ D.oid as dboid,
+ D.datname AS dbname,
+
+ S.db_blks_read AS db_blks_read,
+ S.db_blks_hit AS db_blks_hit,
+ S.total_blks_dirtied AS total_blks_dirtied,
+ S.total_blks_written AS total_blks_written,
+
+ S.wal_records AS wal_records,
+ S.wal_fpi AS wal_fpi,
+ S.wal_bytes AS wal_bytes,
+
+ S.blk_read_time AS blk_read_time,
+ S.blk_write_time AS blk_write_time,
+
+ S.delay_time AS delay_time,
+ S.total_time AS total_time,
+ S.wraparound_failsafe AS wraparound_failsafe,
+ S.errors AS errors
+ FROM
+ pg_database D,
+ LATERAL pg_stat_get_vacuum_database(D.oid) S;
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index d1f3be89b35..bf3cd3b1cc9 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -1815,6 +1815,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
* Tell the cumulative stats system to forget it immediately, too.
*/
pgstat_drop_database(db_id);
+ pgstat_drop_vacuum_database(db_id);
/*
* Except for the deletion of the catalog row, subsequent actions are not
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 43450685b09..c7dd2bb52f6 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -869,7 +869,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
IndexBulkDeleteResult *istat_res;
IndexVacuumInfo ivinfo;
LVExtStatCountersIdx extVacCounters;
- ExtVacReport extVacReport;
+ PgStat_VacuumRelationCounts extVacReport;
/*
* Update the pointer to the corresponding bulk-deletion result if someone
@@ -911,8 +911,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
/* Make extended vacuum stats report for index */
extvac_stats_end_idx(indrel, istat_res, &extVacCounters, &extVacReport);
- pgstat_report_vacuum(indrel,
- 0, 0, 0, &extVacReport);
+ pgstat_report_vacuum_extstats(RelationGetRelid(indrel), indrel->rd_rel->relisshared, &extVacReport);
/*
* Copy the index bulk-deletion result returned from ambulkdelete and
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 9c2443e1ecd..183f7514d2d 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -27,6 +27,7 @@ OBJS = \
pgstat_function.o \
pgstat_io.o \
pgstat_relation.o \
+ pgstat_vacuum.o \
pgstat_replslot.o \
pgstat_shmem.o \
pgstat_slru.o \
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index f317c6e8e90..cdc9cab01cf 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -203,7 +203,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind);
bool pgstat_track_counts = false;
int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE;
-
+bool pgstat_track_vacuum_statistics = false;
/* ----------
* state shared with pgstat_*.c
@@ -482,6 +482,34 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
.reset_all_cb = pgstat_wal_reset_all_cb,
.snapshot_cb = pgstat_wal_snapshot_cb,
},
+ [PGSTAT_KIND_VACUUM_DB] = {
+ .name = "vacuum statistics",
+
+ .fixed_amount = false,
+ .write_to_file = true,
+ /* so pg_stat_database entries can be seen in all databases */
+ .accessed_across_databases = true,
+
+ .shared_size = sizeof(PgStatShared_VacuumDB),
+ .shared_data_off = offsetof(PgStatShared_VacuumDB, stats),
+ .shared_data_len = sizeof(((PgStatShared_VacuumDB *) 0)->stats),
+ .pending_size = sizeof(PgStat_VacuumDBCounts),
+
+ .flush_pending_cb = pgstat_vacuum_db_flush_cb,
+ },
+ [PGSTAT_KIND_VACUUM_RELATION] = {
+ .name = "vacuum statistics",
+
+ .fixed_amount = false,
+ .write_to_file = true,
+
+ .shared_size = sizeof(PgStatShared_VacuumRelation),
+ .shared_data_off = offsetof(PgStatShared_VacuumRelation, stats),
+ .shared_data_len = sizeof(((PgStatShared_VacuumRelation *) 0)->stats),
+ .pending_size = sizeof(PgStat_RelationVacuumPending),
+
+ .flush_pending_cb = pgstat_vacuum_relation_flush_cb
+ },
};
/*
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 65207d30378..80e6c7c229a 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -46,6 +46,15 @@ pgstat_drop_database(Oid databaseid)
pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
}
+/*
+ * Remove entry for the database being dropped.
+ */
+void
+pgstat_drop_vacuum_database(Oid databaseid)
+{
+ pgstat_drop_transactional(PGSTAT_KIND_VACUUM_DB, databaseid, InvalidOid);
+}
+
/*
* Called from autovacuum.c to report startup of an autovacuum process.
* We are called before InitPostgres is done, so can't rely on MyDatabaseId;
@@ -485,7 +494,6 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
pgstat_unlock_entry(entry_ref);
memset(pendingent, 0, sizeof(*pendingent));
- memset(&(pendingent)->vacuum_ext, 0, sizeof(ExtVacReport));
return true;
}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 2675c541369..e8665d23099 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -47,8 +47,6 @@ static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_lev
static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
-static void pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
- bool accumulate_reltype_specific_info);
/*
@@ -210,12 +208,11 @@ pgstat_drop_relation(Relation rel)
*/
void
pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
- PgStat_Counter deadtuples, TimestampTz starttime, ExtVacReport * params)
+ PgStat_Counter deadtuples, TimestampTz starttime)
{
PgStat_EntryRef *entry_ref;
PgStatShared_Relation *shtabentry;
PgStat_StatTabEntry *tabentry;
- PgStatShared_Database *dbentry;
Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
TimestampTz ts;
PgStat_Counter elapsedtime;
@@ -237,8 +234,6 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
tabentry->live_tuples = livetuples;
tabentry->dead_tuples = deadtuples;
- pgstat_accumulate_extvac_stats(&tabentry->vacuum_ext, params, true);
-
/*
* It is quite possible that a non-aggressive VACUUM ended up skipping
* various pages, however, we'll zero the insert counter here regardless.
@@ -274,16 +269,6 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
*/
pgstat_flush_io(false);
(void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
-
- if (dboid != InvalidOid)
- {
- entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
- dboid, InvalidOid, false);
- dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
-
- pgstat_accumulate_extvac_stats(&dbentry->stats.vacuum_ext, params, false);
- pgstat_unlock_entry(entry_ref);
- }
}
/*
@@ -918,6 +903,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
return true;
}
+void
+pgstat_vacuum_relation_delete_pending_cb(Oid relid)
+{
+ pgstat_drop_transactional(PGSTAT_KIND_VACUUM_RELATION, relid, InvalidOid);
+}
+
void
pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
{
@@ -1027,55 +1018,3 @@ restore_truncdrop_counters(PgStat_TableXactStatus *trans)
trans->tuples_deleted = trans->deleted_pre_truncdrop;
}
}
-
-static void
-pgstat_accumulate_extvac_stats(ExtVacReport * dst, ExtVacReport * src,
- bool accumulate_reltype_specific_info)
-{
- dst->total_blks_read += src->total_blks_read;
- dst->total_blks_hit += src->total_blks_hit;
- dst->total_blks_dirtied += src->total_blks_dirtied;
- dst->total_blks_written += src->total_blks_written;
- dst->wal_bytes += src->wal_bytes;
- dst->wal_fpi += src->wal_fpi;
- dst->wal_records += src->wal_records;
- dst->blk_read_time += src->blk_read_time;
- dst->blk_write_time += src->blk_write_time;
- dst->delay_time += src->delay_time;
- dst->total_time += src->total_time;
- dst->wraparound_failsafe_count += src->wraparound_failsafe_count;
-
- if (!accumulate_reltype_specific_info)
- return;
-
- if (dst->type == PGSTAT_EXTVAC_INVALID)
- dst->type = src->type;
-
- Assert(src->type == PGSTAT_EXTVAC_INVALID || src->type == dst->type);
-
- if (dst->type == src->type)
- {
- dst->blks_fetched += src->blks_fetched;
- dst->blks_hit += src->blks_hit;
-
- if (dst->type == PGSTAT_EXTVAC_TABLE)
- {
- dst->table.pages_scanned += src->table.pages_scanned;
- dst->table.pages_removed += src->table.pages_removed;
- dst->table.vm_new_frozen_pages += src->table.vm_new_frozen_pages;
- dst->table.vm_new_visible_pages += src->table.vm_new_visible_pages;
- dst->table.vm_new_visible_frozen_pages += src->table.vm_new_visible_frozen_pages;
- dst->tuples_deleted += src->tuples_deleted;
- dst->table.tuples_frozen += src->table.tuples_frozen;
- dst->table.recently_dead_tuples += src->table.recently_dead_tuples;
- dst->table.index_vacuum_count += src->table.index_vacuum_count;
- dst->table.missed_dead_pages += src->table.missed_dead_pages;
- dst->table.missed_dead_tuples += src->table.missed_dead_tuples;
- }
- else if (dst->type == PGSTAT_EXTVAC_INDEX)
- {
- dst->index.pages_deleted += src->index.pages_deleted;
- dst->tuples_deleted += src->tuples_deleted;
- }
- }
-}
diff --git a/src/backend/utils/activity/pgstat_vacuum.c b/src/backend/utils/activity/pgstat_vacuum.c
new file mode 100644
index 00000000000..340ee24f26a
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_vacuum.c
@@ -0,0 +1,214 @@
+#include "postgres.h"
+
+#include "pgstat.h"
+#include "utils/pgstat_internal.h"
+#include "utils/memutils.h"
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool pgstat_track_vacuum_statistics_for_relations = false;
+
+#define ACCUMULATE_FIELD(field) dst->field += src->field;
+
+#define ACCUMULATE_SUBFIELD(substruct, field) \
+ (dst->substruct.field += src->substruct.field)
+
+static void
+pgstat_accumulate_common(PgStat_CommonCounts * dst, const PgStat_CommonCounts * src)
+{
+ ACCUMULATE_FIELD(total_blks_read);
+ ACCUMULATE_FIELD(total_blks_hit);
+ ACCUMULATE_FIELD(total_blks_dirtied);
+ ACCUMULATE_FIELD(total_blks_written);
+
+ ACCUMULATE_FIELD(blks_fetched);
+ ACCUMULATE_FIELD(blks_hit);
+
+ ACCUMULATE_FIELD(wal_records);
+ ACCUMULATE_FIELD(wal_fpi);
+ ACCUMULATE_FIELD(wal_bytes);
+
+ ACCUMULATE_FIELD(blk_read_time);
+ ACCUMULATE_FIELD(blk_write_time);
+ ACCUMULATE_FIELD(delay_time);
+ ACCUMULATE_FIELD(total_time);
+
+ ACCUMULATE_FIELD(tuples_deleted);
+ ACCUMULATE_FIELD(wraparound_failsafe_count);
+}
+
+static void
+pgstat_accumulate_extvac_stats_relations(PgStat_VacuumRelationCounts * dst, PgStat_VacuumRelationCounts * src)
+{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ if (dst->type == PGSTAT_EXTVAC_INVALID)
+ dst->type = src->type;
+
+ Assert(src->type != PGSTAT_EXTVAC_INVALID && src->type != PGSTAT_EXTVAC_DB && src->type == dst->type);
+
+ pgstat_accumulate_common(&dst->common, &src->common);
+
+ ACCUMULATE_SUBFIELD(common, blks_fetched);
+ ACCUMULATE_SUBFIELD(common, blks_hit);
+
+ if (dst->type == PGSTAT_EXTVAC_TABLE)
+ {
+ ACCUMULATE_SUBFIELD(common, tuples_deleted);
+ ACCUMULATE_SUBFIELD(table, pages_scanned);
+ ACCUMULATE_SUBFIELD(table, pages_removed);
+ ACCUMULATE_SUBFIELD(table, vm_new_frozen_pages);
+ ACCUMULATE_SUBFIELD(table, vm_new_visible_pages);
+ ACCUMULATE_SUBFIELD(table, vm_new_visible_frozen_pages);
+ ACCUMULATE_SUBFIELD(table, tuples_frozen);
+ ACCUMULATE_SUBFIELD(table, recently_dead_tuples);
+ ACCUMULATE_SUBFIELD(table, index_vacuum_count);
+ ACCUMULATE_SUBFIELD(table, missed_dead_pages);
+ ACCUMULATE_SUBFIELD(table, missed_dead_tuples);
+ }
+ else if (dst->type == PGSTAT_EXTVAC_INDEX)
+ {
+ ACCUMULATE_SUBFIELD(common, tuples_deleted);
+ ACCUMULATE_SUBFIELD(index, pages_deleted);
+ }
+}
+
+static void
+pgstat_accumulate_extvac_stats_db(PgStat_VacuumDBCounts * dst, PgStat_VacuumDBCounts * src)
+{
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ pgstat_accumulate_common(&dst->common, &src->common);
+}
+
+/*
+ * Report that the table was just vacuumed and flush statistics.
+ */
+void
+pgstat_report_vacuum_extstats(Oid tableoid, bool shared,
+ PgStat_VacuumRelationCounts * params)
+{
+ PgStat_EntryRef *entry_ref;
+ PgStatShared_VacuumRelation *shtabentry;
+ PgStatShared_VacuumDB *shdbentry;
+ Oid dboid = (shared ? InvalidOid : MyDatabaseId);
+
+ if (!pgstat_track_vacuum_statistics)
+ return;
+
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_VACUUM_RELATION,
+ dboid, tableoid, false);
+ shtabentry = (PgStatShared_VacuumRelation *) entry_ref->shared_stats;
+ pgstat_accumulate_extvac_stats_relations(&shtabentry->stats, params);
+
+ pgstat_unlock_entry(entry_ref);
+
+
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_VACUUM_DB,
+ dboid, InvalidOid, false);
+
+ shdbentry = (PgStatShared_VacuumDB *) entry_ref->shared_stats;
+
+ pgstat_accumulate_common(&shdbentry->stats.common, ¶ms->common);
+
+ pgstat_unlock_entry(entry_ref);
+}
+
+/*
+ * Flush out pending stats for the entry
+ *
+ * If nowait is true, this function returns false if lock could not
+ * immediately acquired, otherwise true is returned.
+ */
+bool
+pgstat_vacuum_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+ PgStatShared_VacuumRelation *shtabstats;
+ PgStat_RelationVacuumPending *pendingent; /* table entry of shared stats */
+
+ pendingent = (PgStat_RelationVacuumPending *) entry_ref->pending;
+ shtabstats = (PgStatShared_VacuumRelation *) entry_ref->shared_stats;
+
+ /*
+ * Ignore entries that didn't accumulate any actual counts.
+ */
+ if (pg_memory_is_all_zeros(&pendingent,
+ sizeof(struct PgStat_RelationVacuumPending)))
+ return true;
+
+ if (!pgstat_lock_entry(entry_ref, nowait))
+ {
+ return false;
+ }
+
+ pgstat_accumulate_extvac_stats_relations(&(shtabstats->stats), &(pendingent->counts));
+
+ pgstat_unlock_entry(entry_ref);
+
+ return true;
+}
+
+/*
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * the vacuum collected statistics for one relation or NULL.
+ */
+PgStat_VacuumRelationCounts *
+pgstat_fetch_stat_vacuum_tabentry(Oid relid, Oid dbid)
+{
+ return (PgStat_VacuumRelationCounts *)
+ pgstat_fetch_entry(PGSTAT_KIND_VACUUM_RELATION, dbid, relid);
+}
+
+PgStat_VacuumDBCounts *
+pgstat_fetch_stat_vacuum_dbentry(Oid dbid)
+{
+ return (PgStat_VacuumDBCounts *)
+ pgstat_fetch_entry(PGSTAT_KIND_VACUUM_DB, dbid, InvalidOid);
+}
+
+bool
+pgstat_vacuum_db_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+ PgStatShared_VacuumDB *sharedent;
+ PgStat_VacuumDBCounts *pendingent;
+
+ pendingent = (PgStat_VacuumDBCounts *) entry_ref->pending;
+ sharedent = (PgStatShared_VacuumDB *) entry_ref->shared_stats;
+
+ if (!pgstat_lock_entry(entry_ref, nowait))
+ return false;
+
+ /* The entry was successfully flushed, add the same to database stats */
+ pgstat_accumulate_extvac_stats_db(&(sharedent->stats), pendingent);
+
+ pgstat_unlock_entry(entry_ref);
+
+ return true;
+}
+
+/*
+ * Find or create a local PgStat_VacuumDBCounts entry for dboid.
+ */
+PgStat_VacuumDBCounts *
+pgstat_prep_vacuum_database_pending(Oid dboid)
+{
+ PgStat_EntryRef *entry_ref;
+
+ /*
+ * This should not report stats on database objects before having
+ * connected to a database.
+ */
+ Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
+
+ entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_VACUUM_DB, dboid, InvalidOid,
+ NULL);
+
+ if (entry_ref == NULL)
+ return NULL;
+
+ return entry_ref->pending;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 4e2714f2e6a..0a64f034a3f 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2314,7 +2314,6 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objid));
}
-
/*
* Get the vacuum statistics for the heap tables.
*/
@@ -2324,41 +2323,45 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 26
Oid relid = PG_GETARG_OID(0);
- PgStat_StatTabEntry *tabentry;
- ExtVacReport *extvacuum;
+ PgStat_VacuumRelationCounts *extvacuum;
+ PgStat_VacuumRelationCounts *pending;
TupleDesc tupdesc;
Datum values[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
bool nulls[PG_STAT_GET_VACUUM_TABLES_STATS_COLS] = {0};
char buf[256];
int i = 0;
+ /* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
- tabentry = pgstat_fetch_stat_tabentry(relid);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, MyDatabaseId);
- if (!tabentry)
- {
- InitMaterializedSRF(fcinfo, 0);
- PG_RETURN_VOID();
- }
- else
+ if (!pending)
{
- extvacuum = &(tabentry->vacuum_ext);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, 0);
+
+ if (!pending)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
}
+ extvacuum = pending;
+
i = 0;
values[i++] = ObjectIdGetDatum(relid);
- values[i++] = Int64GetDatum(extvacuum->total_blks_read);
- values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
- values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
- values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
- values[i++] = Int64GetDatum(extvacuum->blks_fetched -
- extvacuum->blks_hit);
- values[i++] = Int64GetDatum(extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_fetched -
+ extvacuum->common.blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_hit);
values[i++] = Int64GetDatum(extvacuum->table.pages_scanned);
values[i++] = Int64GetDatum(extvacuum->table.pages_removed);
@@ -2366,28 +2369,28 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS)
values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_pages);
values[i++] = Int64GetDatum(extvacuum->table.vm_new_visible_frozen_pages);
values[i++] = Int64GetDatum(extvacuum->table.missed_dead_pages);
- values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+ values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
values[i++] = Int64GetDatum(extvacuum->table.tuples_frozen);
values[i++] = Int64GetDatum(extvacuum->table.recently_dead_tuples);
values[i++] = Int64GetDatum(extvacuum->table.missed_dead_tuples);
- values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+ values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
values[i++] = Int64GetDatum(extvacuum->table.index_vacuum_count);
- values[i++] = Int64GetDatum(extvacuum->wal_records);
- values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
/* Convert to numeric, like pg_stat_statements */
- snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
values[i++] = DirectFunctionCall3(numeric_in,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- values[i++] = Float8GetDatum(extvacuum->blk_read_time);
- values[i++] = Float8GetDatum(extvacuum->blk_write_time);
- values[i++] = Float8GetDatum(extvacuum->delay_time);
- values[i++] = Float8GetDatum(extvacuum->total_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+ values[i++] = Float8GetDatum(extvacuum->common.total_time);
Assert(i == PG_STAT_GET_VACUUM_TABLES_STATS_COLS);
@@ -2404,8 +2407,8 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 16
Oid relid = PG_GETARG_OID(0);
- PgStat_StatTabEntry *tabentry;
- ExtVacReport *extvacuum;
+ PgStat_VacuumRelationCounts *extvacuum;
+ PgStat_VacuumRelationCounts *pending;
TupleDesc tupdesc;
Datum values[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
bool nulls[PG_STAT_GET_VACUUM_INDEX_STATS_COLS] = {0};
@@ -2415,48 +2418,51 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS)
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
- tabentry = pgstat_fetch_stat_tabentry(relid);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, MyDatabaseId);
- if (tabentry == NULL)
- {
- InitMaterializedSRF(fcinfo, 0);
- PG_RETURN_VOID();
- }
- else
+ if (!pending)
{
- extvacuum = &(tabentry->vacuum_ext);
+ pending = pgstat_fetch_stat_vacuum_tabentry(relid, 0);
+
+ if (!pending)
+ {
+ InitMaterializedSRF(fcinfo, 0);
+ PG_RETURN_VOID();
+ }
}
+ extvacuum = pending;
+
i = 0;
values[i++] = ObjectIdGetDatum(relid);
- values[i++] = Int64GetDatum(extvacuum->total_blks_read);
- values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
- values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
- values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
- values[i++] = Int64GetDatum(extvacuum->blks_fetched -
- extvacuum->blks_hit);
- values[i++] = Int64GetDatum(extvacuum->blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_fetched -
+ extvacuum->common.blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.blks_hit);
values[i++] = Int64GetDatum(extvacuum->index.pages_deleted);
- values[i++] = Int64GetDatum(extvacuum->tuples_deleted);
+ values[i++] = Int64GetDatum(extvacuum->common.tuples_deleted);
- values[i++] = Int64GetDatum(extvacuum->wal_records);
- values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
/* Convert to numeric, like pg_stat_statements */
- snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
values[i++] = DirectFunctionCall3(numeric_in,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- values[i++] = Float8GetDatum(extvacuum->blk_read_time);
- values[i++] = Float8GetDatum(extvacuum->blk_write_time);
- values[i++] = Float8GetDatum(extvacuum->delay_time);
- values[i++] = Float8GetDatum(extvacuum->total_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+ values[i++] = Float8GetDatum(extvacuum->common.total_time);
Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS);
@@ -2470,8 +2476,8 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 14
Oid dbid = PG_GETARG_OID(0);
- PgStat_StatDBEntry *dbentry;
- ExtVacReport *extvacuum;
+ PgStat_VacuumDBCounts *extvacuum;
+ PgStat_VacuumDBCounts *pending;
TupleDesc tupdesc;
Datum values[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
bool nulls[PG_STAT_GET_VACUUM_DATABASE_STATS_COLS] = {0};
@@ -2481,42 +2487,41 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS)
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
- dbentry = pgstat_fetch_stat_dbentry(dbid);
+ pending = pgstat_fetch_stat_vacuum_dbentry(dbid);
- if (dbentry == NULL)
+ if (!pending)
{
InitMaterializedSRF(fcinfo, 0);
PG_RETURN_VOID();
}
- else
- {
- extvacuum = &(dbentry->vacuum_ext);
- }
+
+ extvacuum = pending;
i = 0;
values[i++] = ObjectIdGetDatum(dbid);
- values[i++] = Int64GetDatum(extvacuum->total_blks_read);
- values[i++] = Int64GetDatum(extvacuum->total_blks_hit);
- values[i++] = Int64GetDatum(extvacuum->total_blks_dirtied);
- values[i++] = Int64GetDatum(extvacuum->total_blks_written);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_read);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_hit);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_dirtied);
+ values[i++] = Int64GetDatum(extvacuum->common.total_blks_written);
- values[i++] = Int64GetDatum(extvacuum->wal_records);
- values[i++] = Int64GetDatum(extvacuum->wal_fpi);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_records);
+ values[i++] = Int64GetDatum(extvacuum->common.wal_fpi);
/* Convert to numeric, like pg_stat_statements */
- snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->wal_bytes);
+ snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes);
values[i++] = DirectFunctionCall3(numeric_in,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- values[i++] = Float8GetDatum(extvacuum->blk_read_time);
- values[i++] = Float8GetDatum(extvacuum->blk_write_time);
- values[i++] = Float8GetDatum(extvacuum->delay_time);
- values[i++] = Float8GetDatum(extvacuum->total_time);
- values[i++] = Int32GetDatum(extvacuum->wraparound_failsafe_count);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_read_time);
+ values[i++] = Float8GetDatum(extvacuum->common.blk_write_time);
+ values[i++] = Float8GetDatum(extvacuum->common.delay_time);
+ values[i++] = Float8GetDatum(extvacuum->common.total_time);
+ values[i++] = Int32GetDatum(extvacuum->common.wraparound_failsafe_count);
+ values[i++] = Int32GetDatum(extvacuum->errors);
Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS);
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..631df3a57c3 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3084,6 +3084,12 @@
boot_val => 'false',
},
+{ name => 'track_vacuum_statistics', type => 'bool', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
+ short_desc => 'Collects vacuum statistics for vacuum activity.',
+ variable => 'pgstat_track_vacuum_statistics',
+ boot_val => 'false',
+},
+
{ name => 'track_wal_io_timing', type => 'bool', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
short_desc => 'Collects timing statistics for WAL I/O activity.',
variable => 'track_wal_io_timing',
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index b48ace6084b..6e85b08aa89 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -437,5 +437,5 @@ extern double anl_get_next_S(double t, int n, double *stateptr);
extern void extvac_stats_start_idx(Relation rel, IndexBulkDeleteResult *stats,
LVExtStatCountersIdx * counters);
extern void extvac_stats_end_idx(Relation rel, IndexBulkDeleteResult *stats,
- LVExtStatCountersIdx * counters, ExtVacReport * report);
+ LVExtStatCountersIdx * counters, PgStat_VacuumRelationCounts * report);
#endif /* VACUUM_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f3bdc1c38df..61d488f1bf8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -119,54 +119,100 @@ typedef enum ExtVacReportType
{
PGSTAT_EXTVAC_INVALID = 0,
PGSTAT_EXTVAC_TABLE = 1,
- PGSTAT_EXTVAC_INDEX = 2
-} ExtVacReportType;
+ PGSTAT_EXTVAC_INDEX = 2,
+ PGSTAT_EXTVAC_DB = 3,
+} ExtVacReportType;
/* ----------
+ * PgStat_TableCounts The actual per-table counts kept by a backend
*
- * ExtVacReport
+ * This struct should contain only actual event counters, because we make use
+ * of pg_memory_is_all_zeros() to detect whether there are any stats updates
+ * to apply.
*
- * Additional statistics of vacuum processing over a relation.
- * pages_removed is the amount by which the physically shrank,
- * if any (ie the change in its total size on disk)
- * pages_deleted refer to free space within the index file
+ * It is a component of PgStat_TableStatus (within-backend state).
+ *
+ * Note: for a table, tuples_returned is the number of tuples successfully
+ * fetched by heap_getnext, while tuples_fetched is the number of tuples
+ * successfully fetched by heap_fetch under the control of bitmap indexscans.
+ * For an index, tuples_returned is the number of index entries returned by
+ * the index AM, while tuples_fetched is the number of tuples successfully
+ * fetched by heap_fetch under the control of simple indexscans for this index.
+ *
+ * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted
+ * actions, regardless of whether the transaction committed. delta_live_tuples,
+ * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
+ * Note that delta_live_tuples and delta_dead_tuples can be negative!
* ----------
*/
-typedef struct ExtVacReport
+typedef struct PgStat_TableCounts
{
- /*
- * number of blocks missed, hit, dirtied and written during a vacuum of
- * specific relation
- */
+ PgStat_Counter numscans;
+
+ PgStat_Counter tuples_returned;
+ PgStat_Counter tuples_fetched;
+
+ PgStat_Counter tuples_inserted;
+ PgStat_Counter tuples_updated;
+ PgStat_Counter tuples_deleted;
+ PgStat_Counter tuples_hot_updated;
+ PgStat_Counter tuples_newpage_updated;
+ bool truncdropped;
+
+ PgStat_Counter delta_live_tuples;
+ PgStat_Counter delta_dead_tuples;
+ PgStat_Counter changed_tuples;
+
+ PgStat_Counter blocks_fetched;
+ PgStat_Counter blocks_hit;
+
+ PgStat_Counter rev_all_visible_pages;
+ PgStat_Counter rev_all_frozen_pages;
+} PgStat_TableCounts;
+
+typedef struct PgStat_CommonCounts
+{
+ /* blocks */
int64 total_blks_read;
int64 total_blks_hit;
int64 total_blks_dirtied;
int64 total_blks_written;
- /*
- * blocks missed and hit for just the heap during a vacuum of specific
- * relation
- */
+ /* heap blocks */
int64 blks_fetched;
int64 blks_hit;
- /* Vacuum WAL usage stats */
- int64 wal_records; /* wal usage: number of WAL records */
- int64 wal_fpi; /* wal usage: number of WAL full page images
- * produced */
- uint64 wal_bytes; /* wal usage: size of WAL records produced */
+ /* WAL */
+ int64 wal_records;
+ int64 wal_fpi;
+ uint64 wal_bytes;
- /* Time stats. */
- double blk_read_time; /* time spent reading pages, in msec */
- double blk_write_time; /* time spent writing pages, in msec */
- double delay_time; /* how long vacuum slept in vacuum delay
- * point, in msec */
- double total_time; /* total time of a vacuum operation, in msec */
+ /* Time */
+ double blk_read_time;
+ double blk_write_time;
+ double delay_time;
+ double total_time;
- int64 tuples_deleted; /* tuples deleted by vacuum */
+ /* tuples */
+ int64 tuples_deleted;
- int32 wraparound_failsafe_count; /* the number of times to prevent
- * wraparound problem */
+ /* failsafe */
+ int32 wraparound_failsafe_count;
+} PgStat_CommonCounts;
+
+/* ----------
+ *
+ * PgStat_VacuumRelationCounts
+ *
+ * Additional statistics of vacuum processing over a relation.
+ * pages_removed is the amount by which the physically shrank,
+ * if any (ie the change in its total size on disk)
+ * pages_deleted refer to free space within the index file
+ * ----------
+ */
+typedef struct PgStat_VacuumRelationCounts
+{
+ PgStat_CommonCounts common;
ExtVacReportType type; /* heap, index, etc. */
@@ -185,6 +231,13 @@ typedef struct ExtVacReport
{
struct
{
+ int64 tuples_frozen; /* tuples frozen up by vacuum */
+ int64 recently_dead_tuples; /* deleted tuples that are
+ * still visible to some
+ * transaction */
+ int64 missed_dead_tuples; /* tuples not pruned by vacuum due
+ * to failure to get a cleanup
+ * lock */
int64 pages_scanned; /* heap pages examined (not skipped by
* VM) */
int64 pages_removed; /* heap pages removed by vacuum
@@ -192,10 +245,6 @@ typedef struct ExtVacReport
int64 pages_frozen; /* pages marked in VM as frozen */
int64 pages_all_visible; /* pages marked in VM as
* all-visible */
- int64 tuples_frozen; /* tuples frozen up by vacuum */
- int64 recently_dead_tuples; /* deleted tuples that are
- * still visible to some
- * transaction */
int64 vm_new_frozen_pages; /* pages marked in VM as
* frozen */
int64 vm_new_visible_pages; /* pages marked in VM as
@@ -203,9 +252,6 @@ typedef struct ExtVacReport
int64 vm_new_visible_frozen_pages; /* pages marked in VM as
* all-visible and
* frozen */
- int64 missed_dead_tuples; /* tuples not pruned by vacuum due
- * to failure to get a cleanup
- * lock */
int64 missed_dead_pages; /* pages with missed dead tuples */
int64 index_vacuum_count; /* number of index vacuumings */
} table;
@@ -214,60 +260,21 @@ typedef struct ExtVacReport
int64 pages_deleted; /* number of pages deleted by vacuum */
} index;
} /* per_type_stats */ ;
-} ExtVacReport;
+} PgStat_VacuumRelationCounts;
-/* ----------
- * PgStat_TableCounts The actual per-table counts kept by a backend
- *
- * This struct should contain only actual event counters, because we make use
- * of pg_memory_is_all_zeros() to detect whether there are any stats updates
- * to apply.
- *
- * It is a component of PgStat_TableStatus (within-backend state).
- *
- * Note: for a table, tuples_returned is the number of tuples successfully
- * fetched by heap_getnext, while tuples_fetched is the number of tuples
- * successfully fetched by heap_fetch under the control of bitmap indexscans.
- * For an index, tuples_returned is the number of index entries returned by
- * the index AM, while tuples_fetched is the number of tuples successfully
- * fetched by heap_fetch under the control of simple indexscans for this index.
- *
- * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted
- * actions, regardless of whether the transaction committed. delta_live_tuples,
- * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
- * Note that delta_live_tuples and delta_dead_tuples can be negative!
- * ----------
- */
-typedef struct PgStat_TableCounts
+typedef struct PgStat_VacuumRelationStatus
{
- PgStat_Counter numscans;
-
- PgStat_Counter tuples_returned;
- PgStat_Counter tuples_fetched;
-
- PgStat_Counter tuples_inserted;
- PgStat_Counter tuples_updated;
- PgStat_Counter tuples_deleted;
- PgStat_Counter tuples_hot_updated;
- PgStat_Counter tuples_newpage_updated;
- bool truncdropped;
-
- PgStat_Counter delta_live_tuples;
- PgStat_Counter delta_dead_tuples;
- PgStat_Counter changed_tuples;
-
- PgStat_Counter blocks_fetched;
- PgStat_Counter blocks_hit;
-
- PgStat_Counter rev_all_visible_pages;
- PgStat_Counter rev_all_frozen_pages;
+ Oid id; /* table's OID */
+ bool shared; /* is it a shared catalog? */
+ PgStat_VacuumRelationCounts counts; /* event counts to be sent */
+} PgStat_VacuumRelationStatus;
- /*
- * Additional cumulative stat on vacuum operations. Use an expensive
- * structure as an abstraction for different types of relations.
- */
- ExtVacReport vacuum_ext;
-} PgStat_TableCounts;
+typedef struct PgStat_VacuumDBCounts
+{
+ Oid dbjid;
+ PgStat_CommonCounts common;
+ int32 errors;
+} PgStat_VacuumDBCounts;
/* ----------
* PgStat_TableStatus Per-table status within a backend
@@ -293,6 +300,12 @@ typedef struct PgStat_TableStatus
Relation relation; /* rel that is using this entry */
} PgStat_TableStatus;
+typedef struct PgStat_RelationVacuumPending
+{
+ Oid id; /* table's OID */
+ PgStat_VacuumRelationCounts counts; /* event counts to be sent */
+} PgStat_RelationVacuumPending;
+
/* ----------
* PgStat_TableXactStatus Per-table, per-subtransaction status
* ----------
@@ -489,8 +502,6 @@ typedef struct PgStat_StatDBEntry
PgStat_Counter parallel_workers_launched;
TimestampTz stat_reset_timestamp;
-
- ExtVacReport vacuum_ext; /* extended vacuum statistics */
} PgStat_StatDBEntry;
typedef struct PgStat_StatFuncEntry
@@ -578,8 +589,6 @@ typedef struct PgStat_StatTabEntry
PgStat_Counter rev_all_visible_pages;
PgStat_Counter rev_all_frozen_pages;
-
- ExtVacReport vacuum_ext;
} PgStat_StatTabEntry;
/* ------
@@ -788,7 +797,7 @@ extern void pgstat_unlink_relation(Relation rel);
extern void pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
PgStat_Counter deadtuples,
- TimestampTz starttime, ExtVacReport * params);
+ TimestampTz starttime);
extern void pgstat_report_analyze(Relation rel,
PgStat_Counter livetuples, PgStat_Counter deadtuples,
bool resetcounter, TimestampTz starttime);
@@ -924,6 +933,16 @@ extern int pgstat_get_transactional_drops(bool isCommit, struct xl_xact_stats_it
extern void pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, bool is_redo);
+extern void pgstat_drop_vacuum_database(Oid databaseid);
+extern void pgstat_vacuum_relation_delete_pending_cb(Oid relid);
+extern void
+ pgstat_report_vacuum_extstats(Oid tableoid, bool shared,
+ PgStat_VacuumRelationCounts * params);
+extern PgStat_RelationVacuumPending * find_vacuum_relation_entry(Oid relid);
+extern PgStat_VacuumDBCounts * pgstat_prep_vacuum_database_pending(Oid dboid);
+extern PgStat_VacuumRelationCounts * pgstat_fetch_stat_vacuum_tabentry(Oid relid, Oid dbid);
+PgStat_VacuumDBCounts *pgstat_fetch_stat_vacuum_dbentry(Oid dbid);
+
/*
* Functions in pgstat_wal.c
*/
@@ -940,7 +959,8 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void);
extern PGDLLIMPORT bool pgstat_track_counts;
extern PGDLLIMPORT int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_fetch_consistency;
-
+extern PGDLLIMPORT bool pgstat_track_vacuum_statistics;
+extern PGDLLIMPORT bool pgstat_track_vacuum_statistics_for_relations;
/*
* Variables in pgstat_bgwriter.c
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 7dffab8dbdd..4abe70cb54e 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -500,6 +500,18 @@ typedef struct PgStatShared_Relation
PgStat_StatTabEntry stats;
} PgStatShared_Relation;
+typedef struct PgStatShared_VacuumDB
+{
+ PgStatShared_Common header;
+ PgStat_VacuumDBCounts stats;
+} PgStatShared_VacuumDB;
+
+typedef struct PgStatShared_VacuumRelation
+{
+ PgStatShared_Common header;
+ PgStat_VacuumRelationCounts stats;
+} PgStatShared_VacuumRelation;
+
typedef struct PgStatShared_Function
{
PgStatShared_Common header;
@@ -678,6 +690,9 @@ extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind,
extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid);
extern void pgstat_snapshot_fixed(PgStat_Kind kind);
+bool pgstat_vacuum_db_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern bool pgstat_vacuum_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+
/*
* Functions in pgstat_archiver.c
diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h
index eb5f0b3ae6d..52e884fbf8b 100644
--- a/src/include/utils/pgstat_kind.h
+++ b/src/include/utils/pgstat_kind.h
@@ -38,9 +38,11 @@
#define PGSTAT_KIND_IO 10
#define PGSTAT_KIND_SLRU 11
#define PGSTAT_KIND_WAL 12
+#define PGSTAT_KIND_VACUUM_DB 13
+#define PGSTAT_KIND_VACUUM_RELATION 14
#define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
-#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
+#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_VACUUM_RELATION
#define PGSTAT_KIND_BUILTIN_SIZE (PGSTAT_KIND_BUILTIN_MAX + 1)
/* Custom stats kinds */
diff --git a/src/test/recovery/t/050_vacuum_extending_basic_test.pl b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
index bd3cb544e30..e2fd541fd89 100644
--- a/src/test/recovery/t/050_vacuum_extending_basic_test.pl
+++ b/src/test/recovery/t/050_vacuum_extending_basic_test.pl
@@ -28,6 +28,7 @@ $node->init;
# Configure the server logging level for the test
$node->append_conf('postgresql.conf', q{
log_min_messages = notice
+ track_vacuum_statistics = on
});
my $stderr;
@@ -64,8 +65,9 @@ $node->safe_psql($dbname, q{
$node->safe_psql(
$dbname,
- "CREATE TABLE vestat (x int PRIMARY KEY)
+ "CREATE TABLE vestat (x int)
WITH (autovacuum_enabled = off, fillfactor = 10);
+ create index vestat_pkey on vestat (x);
INSERT INTO vestat SELECT x FROM generate_series(1, $size_tab) AS g(x);
ANALYZE vestat;"
);
@@ -115,7 +117,7 @@ sub wait_for_vacuum_stats {
AND
(SELECT (tuples_deleted > $idx_tuples_deleted AND wal_records > $idx_wal_records)
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey');"
+ WHERE indexrelname = 'vestat_pkey');"
);
return 1 if ($result_query eq 't');
@@ -183,7 +185,7 @@ sub fetch_vacuum_stats {
$dbname,
"SELECT tuples_deleted, pages_deleted, wal_records, wal_bytes, wal_fpi
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
$index_base_statistics =~ s/\s*\|\s*/ /g; # transform " | " into space
@@ -321,7 +323,7 @@ sub fetch_error_base_idx_vacuum_statistics {
$dbname,
"SELECT tuples_deleted, pages_deleted
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
$base_statistics =~ s/\s*\|\s*/ /g; # transform " | " in space
my ($cur_tuples_deleted, $cur_pages_deleted) = split /\s+/, $base_statistics;
@@ -343,7 +345,7 @@ sub fetch_error_wal_idx_vacuum_statistics {
$dbname,
"SELECT wal_records, wal_bytes, wal_fpi
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
$wal_raw =~ s/\s*\|\s*/ /g; # transform " | " in space
@@ -707,7 +709,7 @@ $base_stats = $node->safe_psql(
'postgres',
"SELECT count(*) = 0
FROM pg_stat_vacuum_indexes
- WHERE relname = 'vestat_pkey';"
+ WHERE indexrelname = 'vestat_pkey';"
);
ok($base_stats eq 't', 'check the printing index vacuum extended statistics from another database are not available');
@@ -742,6 +744,9 @@ $reloid = $node->safe_psql(
}
);
+# Run VACUUM on shared table to ensure stats entry is created
+$node->safe_psql($dbname, "VACUUM pg_shdepend;");
+
# Check if we can get vacuum statistics for cluster relations (dbid = 0)
$base_stats = $node->safe_psql(
$dbname,
@@ -760,6 +765,10 @@ my $indoid = $node->safe_psql(
}
);
+# Run VACUUM on shared index to ensure stats entry is created
+# Note: VACUUM on the table will also vacuum its indexes
+$node->safe_psql($dbname, "VACUUM pg_shdepend;");
+
$base_stats = $node->safe_psql(
$dbname,
qq{
diff --git a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
index 7528f20098b..2a1c506a22f 100644
--- a/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
+++ b/src/test/recovery/t/051_vacuum_extending_freeze_test.pl
@@ -37,6 +37,7 @@ $node->append_conf('postgresql.conf', q{
vacuum_max_eager_freeze_failure_rate = 1.0
vacuum_failsafe_age = 0
vacuum_multixact_failsafe_age = 0
+ track_vacuum_statistics = on
});
$node->start();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b627c85e332..4e8b8b8a2b1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2330,77 +2330,81 @@ pg_stat_user_tables| SELECT relid,
rev_all_visible_pages
FROM pg_stat_all_tables
WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
-pg_stat_vacuum_database| SELECT db.oid AS dboid,
- db.datname AS dbname,
- stats.db_blks_read,
- stats.db_blks_hit,
- stats.total_blks_dirtied,
- stats.total_blks_written,
- stats.wal_records,
- stats.wal_fpi,
- stats.wal_bytes,
- stats.blk_read_time,
- stats.blk_write_time,
- stats.delay_time,
- stats.total_time,
- stats.wraparound_failsafe,
- stats.errors
- FROM pg_database db,
- LATERAL pg_stat_get_vacuum_database(db.oid) stats(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
-pg_stat_vacuum_indexes| SELECT rel.oid AS relid,
- ns.nspname AS schemaname,
- rel.relname,
- stats.total_blks_read,
- stats.total_blks_hit,
- stats.total_blks_dirtied,
- stats.total_blks_written,
- stats.rel_blks_read,
- stats.rel_blks_hit,
- stats.pages_deleted,
- stats.tuples_deleted,
- stats.wal_records,
- stats.wal_fpi,
- stats.wal_bytes,
- stats.blk_read_time,
- stats.blk_write_time,
- stats.delay_time,
- stats.total_time
- FROM (pg_class rel
- JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
- LATERAL pg_stat_get_vacuum_indexes(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
- WHERE (rel.relkind = 'i'::"char");
-pg_stat_vacuum_tables| SELECT ns.nspname AS schemaname,
- rel.relname,
- stats.relid,
- stats.total_blks_read,
- stats.total_blks_hit,
- stats.total_blks_dirtied,
- stats.total_blks_written,
- stats.rel_blks_read,
- stats.rel_blks_hit,
- stats.pages_scanned,
- stats.pages_removed,
- stats.vm_new_frozen_pages,
- stats.vm_new_visible_pages,
- stats.vm_new_visible_frozen_pages,
- stats.missed_dead_pages,
- stats.tuples_deleted,
- stats.tuples_frozen,
- stats.recently_dead_tuples,
- stats.missed_dead_tuples,
- stats.wraparound_failsafe,
- stats.index_vacuum_count,
- stats.wal_records,
- stats.wal_fpi,
- stats.wal_bytes,
- stats.blk_read_time,
- stats.blk_write_time,
- stats.delay_time,
- stats.total_time
- FROM (pg_class rel
- JOIN pg_namespace ns ON ((ns.oid = rel.relnamespace))),
- LATERAL pg_stat_get_vacuum_tables(rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
- WHERE (rel.relkind = 'r'::"char");
+pg_stat_vacuum_database| SELECT d.oid AS dboid,
+ d.datname AS dbname,
+ s.db_blks_read,
+ s.db_blks_hit,
+ s.total_blks_dirtied,
+ s.total_blks_written,
+ s.wal_records,
+ s.wal_fpi,
+ s.wal_bytes,
+ s.blk_read_time,
+ s.blk_write_time,
+ s.delay_time,
+ s.total_time,
+ s.wraparound_failsafe,
+ s.errors
+ FROM pg_database d,
+ LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, errors);
+pg_stat_vacuum_indexes| SELECT c.oid AS relid,
+ i.oid AS indexrelid,
+ n.nspname AS schemaname,
+ c.relname,
+ i.relname AS indexrelname,
+ s.total_blks_read,
+ s.total_blks_hit,
+ s.total_blks_dirtied,
+ s.total_blks_written,
+ s.rel_blks_read,
+ s.rel_blks_hit,
+ s.pages_deleted,
+ s.tuples_deleted,
+ s.wal_records,
+ s.wal_fpi,
+ s.wal_bytes,
+ s.blk_read_time,
+ s.blk_write_time,
+ s.delay_time,
+ s.total_time
+ FROM (((pg_class c
+ JOIN pg_index x ON ((c.oid = x.indrelid)))
+ JOIN pg_class i ON ((i.oid = x.indexrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_stat_get_vacuum_indexes(i.oid) s(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_deleted, tuples_deleted, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
+pg_stat_vacuum_tables| SELECT n.nspname AS schemaname,
+ c.relname,
+ s.relid,
+ s.total_blks_read,
+ s.total_blks_hit,
+ s.total_blks_dirtied,
+ s.total_blks_written,
+ s.rel_blks_read,
+ s.rel_blks_hit,
+ s.pages_scanned,
+ s.pages_removed,
+ s.vm_new_frozen_pages,
+ s.vm_new_visible_pages,
+ s.vm_new_visible_frozen_pages,
+ s.missed_dead_pages,
+ s.tuples_deleted,
+ s.tuples_frozen,
+ s.recently_dead_tuples,
+ s.missed_dead_tuples,
+ s.wraparound_failsafe,
+ s.index_vacuum_count,
+ s.wal_records,
+ s.wal_fpi,
+ s.wal_bytes,
+ s.blk_read_time,
+ s.blk_write_time,
+ s.delay_time,
+ s.total_time
+ FROM (pg_class c
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, missed_dead_pages, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_tuples, wraparound_failsafe, index_vacuum_count, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, total_time)
+ WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
pg_stat_wal| SELECT wal_records,
wal_fpi,
wal_bytes,
--
2.39.5 (Apple Git-154)
[text/plain] v26-0005-Add-documentation-about-the-system-views-that-are-us.patch (24.5K, 7-v26-0005-Add-documentation-about-the-system-views-that-are-us.patch)
download | inline diff:
From cfbf50c85ae9bfdfb9167dab446bf3256e33ddb1 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Thu, 19 Dec 2024 12:57:49 +0300
Subject: [PATCH 5/5] Add documentation about the system views that are used in
the machinery of vacuum statistics.
---
doc/src/sgml/system-views.sgml | 755 +++++++++++++++++++++++++++++++++
1 file changed, 755 insertions(+)
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 162c76b729a..50578cae90d 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -5654,4 +5654,759 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</table>
</sect1>
+<sect1 id="view-pg-stat-vacuum-database">
+ <title><structname>pg_stat_vacuum_database</structname></title>
+
+ <indexterm zone="view-pg-stat-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>wraparound_failsafe_count</structfield> <type>int4</type>
+ </para>
+ <para>
+ Number of times the vacuum was run to prevent a wraparound problem.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>errors</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-stat-vacuum-indexes">
+ <title><structname>pg_stat_vacuum_indexes</structname></title>
+
+ <indexterm zone="view-pg-stat-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>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
+ <sect1 id="view-pg-stat-vacuum-tables">
+ <title><structname>pg_stat_vacuum_tables</structname></title>
+
+ <indexterm zone="view-pg-stat-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 blocks written directly by vacuum or auto vacuum.
+ Blocks that are dirtied by a vacuum process can be written out by another process.
+ </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>vm_new_frozen_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of the number of pages newly set all-frozen by vacuum
+ in the visibility map.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>vm_new_visible_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of the number of pages newly set all-visible by vacuum
+ in the visibility map.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>vm_new_visible_frozen_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of the number of pages newly set all-visible and all-frozen
+ by vacuum 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>recently_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>missed_dead_tuples</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of fully DEAD (not just RECENTLY_DEAD) tuples that could not be
+ pruned due to failure to acquire a cleanup lock on a heap page.
+ </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>wraparound_failsafe_count</structfield> <type>int4</type>
+ </para>
+ <para>
+ Number of times the vacuum was run to prevent a wraparound problem.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>missed_dead_pages</structfield> <type>int8</type>
+ </para>
+ <para>
+ Number of pages that had at least one missed_dead_tuples.
+ </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>
+
+ </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.39.5 (Apple Git-154)
view thread (53+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Vacuum statistics
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox