From f995078939b0180ef6e59603297b35db7031fbfb Mon Sep 17 00:00:00 2001 From: Alena Rybakina Date: Wed, 17 Jun 2026 21:54:17 +0300 Subject: [PATCH 9/9] Extended vacuum statistics: timing metrics for tables, indexes and database Expose the timing counters in pg_stat_vacuum_tables, pg_stat_vacuum_indexes and pg_stat_vacuum_database, with documentation and regression coverage: blk_read_time time spent reading blocks (with track_io_timing) blk_write_time time spent writing blocks (with track_io_timing) delay_time time spent in vacuum cost-based delay total_time total wall-clock time of the vacuum total_time is always positive; the regression test also exercises the positive delay_time path with a dedicated cost-delayed vacuum. --- doc/src/sgml/system-views.sgml | 97 ++++++++++++++++++++++ src/backend/access/heap/vacuumlazy.c | 42 ++++++++-- src/backend/catalog/system_views.sql | 18 ++++ src/backend/commands/vacuumparallel.c | 1 + src/backend/utils/activity/pgstat_vacuum.c | 5 ++ src/backend/utils/adt/pgstatfuncs.c | 22 ++++- src/include/catalog/pg_proc.dat | 18 ++-- src/include/pgstat.h | 6 ++ src/test/regress/expected/rules.out | 18 +++- src/test/regress/expected/vacuum_stats.out | 76 +++++++++++++---- src/test/regress/sql/vacuum_stats.sql | 47 +++++++++-- 11 files changed, 304 insertions(+), 46 deletions(-) diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 2a93896d5d..e15f4952cc 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -5964,6 +5964,38 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx Number of this relation's blocks found in shared buffers by the vacuum. + + + blk_read_time double precision + + + Time spent reading blocks by the vacuum, in milliseconds (when track_io_timing is enabled). + + + + + blk_write_time double precision + + + Time spent writing (flushing) blocks by the vacuum, in milliseconds; remains zero if no flushes occurred. + + + + + delay_time double precision + + + Total time the vacuum spent sleeping in vacuum delay points, in milliseconds. + + + + + total_time double precision + + + Total wall-clock time of the vacuum, in milliseconds, including time spent waiting for I/O and locks. + + wraparound_failsafe integer @@ -6135,6 +6167,38 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx Number of block hits within this index during the vacuum. + + + blk_read_time double precision + + + Time spent reading index blocks, in milliseconds (if track_io_timing is enabled). + + + + + blk_write_time double precision + + + Time spent writing index blocks, in milliseconds (if track_io_timing is enabled). + + + + + delay_time double precision + + + Time spent in vacuum cost-based delay while processing this index, in milliseconds. + + + + + total_time double precision + + + Total time spent vacuuming this index, in milliseconds. + + wal_records bigint @@ -6257,6 +6321,38 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx Number of vacuum operations in this database that engaged the wraparound failsafe mechanism. + + + blk_read_time double precision + + + Time spent reading blocks by vacuum operations in this database, in milliseconds (if track_io_timing is enabled). + + + + + blk_write_time double precision + + + Time spent writing blocks by vacuum operations in this database, in milliseconds (if track_io_timing is enabled). + + + + + delay_time double precision + + + Time spent in vacuum cost-based delay by vacuum operations in this database, in milliseconds. + + + + + total_time double precision + + + Total time spent by vacuum operations in this database, in milliseconds. + + wal_records bigint @@ -6281,6 +6377,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx Total amount of WAL generated by vacuum operations in this database, in bytes. + interrupts_count integer diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 683bfcf9e8..6ae2ccb2de 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -512,13 +512,19 @@ static void restore_vacuum_error_info(LVRelState *vacrel, static void extvac_stats_start(Relation rel, LVExtStatCounters * counters) { + TimestampTz starttime; + if (!pgstat_track_vacuum_statistics) return; 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; @@ -546,6 +552,9 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters, { WalUsage walusage; BufferUsage bufusage; + TimestampTz endtime; + long secs; + int usecs; if (!pgstat_track_vacuum_statistics) return; @@ -559,6 +568,9 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters, memset(&bufusage, 0, sizeof(BufferUsage)); BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &counters->bufusage); + endtime = GetCurrentTimestamp(); + TimestampDifference(counters->starttime, endtime, &secs, &usecs); + /* * Fill additional statistics on a vacuum processing operation. */ @@ -566,10 +578,19 @@ extvac_stats_end(Relation rel, LVExtStatCounters * counters, 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) /* @@ -669,6 +690,8 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCount extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages; extVacStats->common.wraparound_failsafe_count = vacrel->wraparound_failsafe; + extVacStats->common.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; @@ -676,12 +699,17 @@ accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCount 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->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, PgStat_VacuumRelationCounts * extVacIdxStats) { /* Fill heap-specific extended stats fields */ + 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; @@ -689,6 +717,8 @@ accumulate_idxs_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCount 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; } /* @@ -716,6 +746,10 @@ extvac_accumulate_idx_report(PgStat_VacuumRelationCounts * dst, dst->common.wal_records += src->common.wal_records; dst->common.wal_fpi += src->common.wal_fpi; dst->common.wal_bytes += src->common.wal_bytes; + dst->common.blk_read_time += src->common.blk_read_time; + dst->common.blk_write_time += src->common.blk_write_time; + dst->common.delay_time += src->common.delay_time; + dst->common.total_time += src->common.total_time; dst->common.tuples_deleted += src->common.tuples_deleted; dst->index.pages_deleted += src->index.pages_deleted; @@ -904,7 +938,6 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, Size dead_items_max_bytes = 0; LVExtStatCounters extVacCounters; PgStat_VacuumRelationCounts extVacReport; - TimestampTz starttime; /* Initialize vacuum statistics */ memset(&extVacReport, 0, sizeof(PgStat_VacuumRelationCounts)); @@ -922,7 +955,6 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, } } - starttime = GetCurrentTimestamp(); extvac_stats_start(rel, &extVacCounters); pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM, @@ -1279,7 +1311,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, Max(vacrel->new_live_tuples, 0), vacrel->recently_dead_tuples + vacrel->missed_dead_tuples, - starttime); + extVacCounters.starttime); pgstat_progress_end_command(); if (instrument) @@ -1287,7 +1319,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, TimestampTz endtime = GetCurrentTimestamp(); if (verbose || params->log_vacuum_min_duration == 0 || - TimestampDifferenceExceeds(starttime, endtime, + TimestampDifferenceExceeds(extVacCounters.starttime, endtime, params->log_vacuum_min_duration)) { long secs_dur; @@ -1303,7 +1335,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, int64 total_blks_read; int64 total_blks_dirtied; - TimestampDifference(starttime, endtime, &secs_dur, &usecs_dur); + TimestampDifference(extVacCounters.starttime, endtime, &secs_dur, &usecs_dur); memset(&walusage, 0, sizeof(WalUsage)); WalUsageAccumDiff(&walusage, &pgWalUsage, &startwalusage); memset(&bufferusage, 0, sizeof(BufferUsage)); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index d0a58f221c..11029edae3 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1582,6 +1582,10 @@ CREATE VIEW pg_stat_vacuum_tables AS 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.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.wal_records AS wal_records, S.wal_fpi AS wal_fpi, @@ -1607,8 +1611,15 @@ CREATE VIEW pg_stat_vacuum_indexes AS 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.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.wal_records AS wal_records, S.wal_fpi AS wal_fpi, S.wal_bytes AS wal_bytes @@ -1631,7 +1642,14 @@ CREATE VIEW pg_stat_vacuum_database AS S.db_blks_hit AS db_blks_hit, S.total_blks_dirtied AS total_blks_dirtied, S.total_blks_written AS total_blks_written, + S.wraparound_failsafe AS wraparound_failsafe, + + S.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.wal_records AS wal_records, S.wal_fpi AS wal_fpi, S.wal_bytes AS wal_bytes, diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index ea13859485..563a1db912 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -1355,6 +1355,7 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) VacuumUpdateCosts(); VacuumCostBalance = 0; + VacuumDelayTime = 0; VacuumCostBalanceLocal = 0; VacuumSharedCostBalance = &(shared->cost_balance); VacuumActiveNWorkers = &(shared->active_nworkers); diff --git a/src/backend/utils/activity/pgstat_vacuum.c b/src/backend/utils/activity/pgstat_vacuum.c index b1c747f255..c5ea644060 100644 --- a/src/backend/utils/activity/pgstat_vacuum.c +++ b/src/backend/utils/activity/pgstat_vacuum.c @@ -50,6 +50,11 @@ pgstat_accumulate_common(PgStat_CommonCounts *dst, const PgStat_CommonCounts *sr 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(interrupts_count); } diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 7473ae1eb6..2d13604bd0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -2374,7 +2374,7 @@ pg_stat_have_stats(PG_FUNCTION_ARGS) Datum pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS) { -#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 21 +#define PG_STAT_GET_VACUUM_TABLES_STATS_COLS 25 Oid relid = PG_GETARG_OID(0); PgStat_VacuumRelationCounts *extvacuum; @@ -2417,6 +2417,10 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS) values[i++] = Int64GetDatum(extvacuum->common.total_blks_written); values[i++] = Int64GetDatum(extvacuum->common.blks_fetched - extvacuum->common.blks_hit); values[i++] = Int64GetDatum(extvacuum->common.blks_hit); + 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++] = Int64GetDatum(extvacuum->common.wal_records); values[i++] = Int64GetDatum(extvacuum->common.wal_fpi); @@ -2438,7 +2442,7 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS) Datum pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS) { -#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 12 +#define PG_STAT_GET_VACUUM_INDEX_STATS_COLS 16 Oid relid = PG_GETARG_OID(0); PgStat_VacuumRelationCounts *extvacuum; @@ -2475,6 +2479,11 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS) values[i++] = Int64GetDatum(extvacuum->common.blks_fetched - extvacuum->common.blks_hit); values[i++] = Int64GetDatum(extvacuum->common.blks_hit); + 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++] = Int64GetDatum(extvacuum->common.wal_records); values[i++] = Int64GetDatum(extvacuum->common.wal_fpi); snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes); @@ -2482,6 +2491,7 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS) CStringGetDatum(buf), ObjectIdGetDatum(0), Int32GetDatum(-1)); + Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS); /* Returns the record as Datum */ @@ -2494,7 +2504,7 @@ pg_stat_get_vacuum_indexes(PG_FUNCTION_ARGS) Datum pg_stat_get_vacuum_database(PG_FUNCTION_ARGS) { -#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 11 +#define PG_STAT_GET_VACUUM_DATABASE_STATS_COLS 15 Oid dbid = PG_GETARG_OID(0); PgStat_VacuumDBCounts *extvacuum; @@ -2525,6 +2535,11 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS) values[i++] = Int32GetDatum(extvacuum->common.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++] = Int64GetDatum(extvacuum->common.wal_records); values[i++] = Int64GetDatum(extvacuum->common.wal_fpi); snprintf(buf, sizeof buf, UINT64_FORMAT, extvacuum->common.wal_bytes); @@ -2533,6 +2548,7 @@ pg_stat_get_vacuum_database(PG_FUNCTION_ARGS) ObjectIdGetDatum(0), Int32GetDatum(-1)); values[i++] = Int32GetDatum(extvacuum->common.interrupts_count); + Assert(i == PG_STAT_GET_VACUUM_DATABASE_STATS_COLS); /* Returns the record as Datum */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 743ce9dfd6..e02d386944 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12643,9 +12643,9 @@ proname => 'pg_stat_get_vacuum_tables', prorows => 1000, provolatile => 's', prorettype => 'record', proisstrict => 'f', proretset => 't', proargtypes => 'oid', - proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int4,int8,int8,numeric}', - proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{reloid,relid,pages_scanned,pages_removed,tuples_deleted,tuples_frozen,recently_dead_tuples,missed_dead_pages,missed_dead_tuples,vm_new_frozen_pages,vm_new_visible_pages,vm_new_visible_frozen_pages,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,wraparound_failsafe,wal_records,wal_fpi,wal_bytes}', + proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,float8,float8,float8,float8,int4,int8,int8,numeric}', + proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{reloid,relid,pages_scanned,pages_removed,tuples_deleted,tuples_frozen,recently_dead_tuples,missed_dead_pages,missed_dead_tuples,vm_new_frozen_pages,vm_new_visible_pages,vm_new_visible_frozen_pages,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,blk_read_time,blk_write_time,delay_time,total_time,wraparound_failsafe,wal_records,wal_fpi,wal_bytes}', prosrc => 'pg_stat_get_vacuum_tables' } # oid8 related functions @@ -12718,17 +12718,17 @@ proname => 'pg_stat_get_vacuum_indexes', prorows => 1000, provolatile => 's', prorettype => 'record', proisstrict => 'f', proretset => 't', proargtypes => 'oid', - proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric}', - proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{reloid,relid,pages_deleted,tuples_deleted,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,wal_records,wal_fpi,wal_bytes}', + proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,float8,float8,float8,float8,int8,int8,numeric}', + proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{reloid,relid,pages_deleted,tuples_deleted,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,blk_read_time,blk_write_time,delay_time,total_time,wal_records,wal_fpi,wal_bytes}', prosrc => 'pg_stat_get_vacuum_indexes' }, { oid => '8005', descr => 'pg_stat_get_vacuum_database returns vacuum stats values for database', proname => 'pg_stat_get_vacuum_database', prorows => 1000, provolatile => 's', prorettype => 'record', proisstrict => 'f', proretset => 't', proargtypes => 'oid', - proallargtypes => '{oid,oid,int4,int8,int8,int8,int8,int4,int8,int8,numeric,int4}', - proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{dbid,dboid,errors,db_blks_read,db_blks_hit,total_blks_dirtied,total_blks_written,wraparound_failsafe,wal_records,wal_fpi,wal_bytes,interrupts_count}', + proallargtypes => '{oid,oid,int4,int8,int8,int8,int8,int4,float8,float8,float8,float8,int8,int8,numeric,int4}', + proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{dbid,dboid,errors,db_blks_read,db_blks_hit,total_blks_dirtied,total_blks_written,wraparound_failsafe,blk_read_time,blk_write_time,delay_time,total_time,wal_records,wal_fpi,wal_bytes,interrupts_count}', prosrc => 'pg_stat_get_vacuum_database' }, ] diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 18fa9ac5ba..6a9e87cf0b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -190,6 +190,12 @@ typedef struct PgStat_CommonCounts int64 wal_fpi; uint64 wal_bytes; + /* Time */ + double blk_read_time; + double blk_write_time; + double delay_time; + double total_time; + /* tuples */ int64 tuples_deleted; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index c650cfd664..942361dd42 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2429,12 +2429,16 @@ pg_stat_vacuum_database| SELECT d.oid AS dboid, s.total_blks_dirtied, s.total_blks_written, s.wraparound_failsafe, + s.blk_read_time, + s.blk_write_time, + s.delay_time, + s.total_time, s.wal_records, s.wal_fpi, s.wal_bytes, s.interrupts_count FROM pg_database d, - LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, errors, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wraparound_failsafe, wal_records, wal_fpi, wal_bytes, interrupts_count); + LATERAL pg_stat_get_vacuum_database(d.oid) s(dboid, errors, db_blks_read, db_blks_hit, total_blks_dirtied, total_blks_written, wraparound_failsafe, blk_read_time, blk_write_time, delay_time, total_time, wal_records, wal_fpi, wal_bytes, interrupts_count); pg_stat_vacuum_indexes| SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, @@ -2448,6 +2452,10 @@ pg_stat_vacuum_indexes| SELECT c.oid AS relid, s.total_blks_written, s.rel_blks_read, s.rel_blks_hit, + s.blk_read_time, + s.blk_write_time, + s.delay_time, + s.total_time, s.wal_records, s.wal_fpi, s.wal_bytes @@ -2455,7 +2463,7 @@ pg_stat_vacuum_indexes| SELECT c.oid AS relid, JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))), - LATERAL pg_stat_get_vacuum_indexes(i.oid) s(relid, pages_deleted, tuples_deleted, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, wal_records, wal_fpi, wal_bytes) + LATERAL pg_stat_get_vacuum_indexes(i.oid) s(relid, pages_deleted, tuples_deleted, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, blk_read_time, blk_write_time, delay_time, total_time, wal_records, wal_fpi, wal_bytes) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])); pg_stat_vacuum_tables| SELECT n.nspname AS schemaname, c.relname, @@ -2476,13 +2484,17 @@ pg_stat_vacuum_tables| SELECT n.nspname AS schemaname, s.total_blks_written, s.rel_blks_read, s.rel_blks_hit, + s.blk_read_time, + s.blk_write_time, + s.delay_time, + s.total_time, s.wraparound_failsafe, s.wal_records, s.wal_fpi, s.wal_bytes FROM (pg_class c JOIN pg_namespace n ON ((n.oid = c.relnamespace))), - LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, pages_scanned, pages_removed, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_pages, missed_dead_tuples, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, wraparound_failsafe, wal_records, wal_fpi, wal_bytes) + LATERAL pg_stat_get_vacuum_tables(c.oid) s(relid, pages_scanned, pages_removed, tuples_deleted, tuples_frozen, recently_dead_tuples, missed_dead_pages, missed_dead_tuples, vm_new_frozen_pages, vm_new_visible_pages, vm_new_visible_frozen_pages, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, blk_read_time, blk_write_time, delay_time, total_time, wraparound_failsafe, wal_records, wal_fpi, wal_bytes) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])); pg_stat_wal| SELECT wal_records, wal_fpi, diff --git a/src/test/regress/expected/vacuum_stats.out b/src/test/regress/expected/vacuum_stats.out index 3d823ef7b6..bd3c1512d8 100644 --- a/src/test/regress/expected/vacuum_stats.out +++ b/src/test/regress/expected/vacuum_stats.out @@ -148,6 +148,49 @@ SELECT rel_blks_read >= 0 AS rel_blks_read, t | t (1 row) +-- timing metrics and failsafe. The vacuum always takes some wall-clock time +-- (total_time > 0) and does not engage the wraparound failsafe under normal +-- conditions (wraparound_failsafe = 0). blk_read_time/blk_write_time are only +-- non-zero when track_io_timing is enabled, and delay_time only when a vacuum +-- cost delay is configured, so those are merely checked for being +-- non-negative; the positive delay_time path is exercised separately below. +SELECT blk_read_time >= 0 AS blk_read_time, + blk_write_time >= 0 AS blk_write_time, + delay_time >= 0 AS delay_time, + total_time > 0 AS total_time, + wraparound_failsafe = 0 AS wraparound_failsafe + FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t'; + blk_read_time | blk_write_time | delay_time | total_time | wraparound_failsafe +---------------+----------------+------------+------------+--------------------- + t | t | t | t | t +(1 row) + +-- delay path: with a vacuum cost delay configured, a vacuum that accrues cost +-- sleeps, so delay_time advances. +CREATE TABLE vacstat_delay (id int PRIMARY KEY, v text) + WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vacstat_delay SELECT g, repeat('x', 100) FROM generate_series(1, 3000) g; +DELETE FROM vacstat_delay WHERE id % 2 = 0; +SET vacuum_cost_delay = '1ms'; +SET vacuum_cost_limit = 1; +VACUUM vacstat_delay; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +RESET vacuum_cost_delay; +RESET vacuum_cost_limit; +SELECT delay_time > 0 AS delay_time, + total_time > 0 AS total_time + FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_delay'; + delay_time | total_time +------------+------------ + t | t +(1 row) + +DROP TABLE vacstat_delay; -- WAL metrics. A vacuum that removes tuples always emits WAL -- (wal_records > 0, wal_bytes > 0). wal_fpi depends on whether a checkpoint -- happened recently, so it is only checked for being non-negative here; the @@ -186,17 +229,6 @@ SELECT wal_records > 0 AS wal_records, (1 row) DROP TABLE vacstat_fpi; --- wraparound failsafe. A normal vacuum does not engage the wraparound --- failsafe (wraparound_failsafe = 0). The positive path requires reaching the --- failsafe XID age and is covered by a separate TAP test under --- src/test/modules/xid_wraparound. -SELECT wraparound_failsafe = 0 AS wraparound_failsafe - FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t'; - wraparound_failsafe ---------------------- - t -(1 row) - -- per-index view: the primary key index is processed by the same VACUUM. -- No btree leaf empties out (interleaved deletions), so pages_deleted = 0, -- while every index entry for a removed heap tuple is deleted. The index is @@ -211,13 +243,17 @@ SELECT indexrelname, total_blks_written >= 0 AS total_blks_written, rel_blks_read >= 0 AS rel_blks_read, rel_blks_hit > 0 AS rel_blks_hit, + blk_read_time >= 0 AS blk_read_time, + blk_write_time >= 0 AS blk_write_time, + delay_time >= 0 AS delay_time, + total_time > 0 AS total_time, wal_records > 0 AS wal_records, wal_fpi >= 0 AS wal_fpi, wal_bytes > 0 AS wal_bytes FROM pg_stat_vacuum_indexes WHERE relname = 'vacstat_t' ORDER BY indexrelname; - indexrelname | pages_deleted | tuples_deleted | total_blks_read | total_blks_hit | total_blks_dirtied | total_blks_written | rel_blks_read | rel_blks_hit | wal_records | wal_fpi | wal_bytes -----------------+---------------+----------------+-----------------+----------------+--------------------+--------------------+---------------+--------------+-------------+---------+----------- - vacstat_t_pkey | t | t | t | t | t | t | t | t | t | t | t + indexrelname | pages_deleted | tuples_deleted | total_blks_read | total_blks_hit | total_blks_dirtied | total_blks_written | rel_blks_read | rel_blks_hit | blk_read_time | blk_write_time | delay_time | total_time | wal_records | wal_fpi | wal_bytes +----------------+---------------+----------------+-----------------+----------------+--------------------+--------------------+---------------+--------------+---------------+----------------+------------+------------+-------------+---------+----------- + vacstat_t_pkey | t | t | t | t | t | t | t | t | t | t | t | t | t | t | t (1 row) -- index page-deletion path: deleting a contiguous key range empties whole @@ -253,20 +289,24 @@ SELECT indexrelname, DROP TABLE vacstat_idxdel; -- per-database aggregate view: no vacuum errors occurred in this database, and -- the vacuums in this database touched pages through the buffer cache --- (db_blks_hit > 0) and emit WAL (wal_records > 0). +-- (db_blks_hit > 0). SELECT errors = 0 AS errors, db_blks_read >= 0 AS db_blks_read, db_blks_hit > 0 AS db_blks_hit, total_blks_dirtied >= 0 AS total_blks_dirtied, total_blks_written >= 0 AS total_blks_written, wraparound_failsafe = 0 AS wraparound_failsafe, + blk_read_time >= 0 AS blk_read_time, + blk_write_time >= 0 AS blk_write_time, + delay_time >= 0 AS delay_time, + total_time > 0 AS total_time, wal_records > 0 AS wal_records, wal_fpi >= 0 AS wal_fpi, wal_bytes > 0 AS wal_bytes FROM pg_stat_vacuum_database WHERE dbname = current_database(); - errors | db_blks_read | db_blks_hit | total_blks_dirtied | total_blks_written | wraparound_failsafe | wal_records | wal_fpi | wal_bytes ---------+--------------+-------------+--------------------+--------------------+---------------------+-------------+---------+----------- - t | t | t | t | t | t | t | t | t + errors | db_blks_read | db_blks_hit | total_blks_dirtied | total_blks_written | wraparound_failsafe | blk_read_time | blk_write_time | delay_time | total_time | wal_records | wal_fpi | wal_bytes +--------+--------------+-------------+--------------------+--------------------+---------------------+---------------+----------------+------------+------------+-------------+---------+----------- + t | t | t | t | t | t | t | t | t | t | t | t | t (1 row) -- parallel index vacuum: index statistics must be captured for indexes diff --git a/src/test/regress/sql/vacuum_stats.sql b/src/test/regress/sql/vacuum_stats.sql index 40f7d0985d..baf5acdafd 100644 --- a/src/test/regress/sql/vacuum_stats.sql +++ b/src/test/regress/sql/vacuum_stats.sql @@ -98,6 +98,36 @@ SELECT rel_blks_read >= 0 AS rel_blks_read, rel_blks_hit > 0 AS rel_blks_hit FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t'; +-- timing metrics and failsafe. The vacuum always takes some wall-clock time +-- (total_time > 0) and does not engage the wraparound failsafe under normal +-- conditions (wraparound_failsafe = 0). blk_read_time/blk_write_time are only +-- non-zero when track_io_timing is enabled, and delay_time only when a vacuum +-- cost delay is configured, so those are merely checked for being +-- non-negative; the positive delay_time path is exercised separately below. +SELECT blk_read_time >= 0 AS blk_read_time, + blk_write_time >= 0 AS blk_write_time, + delay_time >= 0 AS delay_time, + total_time > 0 AS total_time, + wraparound_failsafe = 0 AS wraparound_failsafe + FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t'; + +-- delay path: with a vacuum cost delay configured, a vacuum that accrues cost +-- sleeps, so delay_time advances. +CREATE TABLE vacstat_delay (id int PRIMARY KEY, v text) + WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vacstat_delay SELECT g, repeat('x', 100) FROM generate_series(1, 3000) g; +DELETE FROM vacstat_delay WHERE id % 2 = 0; +SET vacuum_cost_delay = '1ms'; +SET vacuum_cost_limit = 1; +VACUUM vacstat_delay; +SELECT pg_stat_force_next_flush(); +RESET vacuum_cost_delay; +RESET vacuum_cost_limit; +SELECT delay_time > 0 AS delay_time, + total_time > 0 AS total_time + FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_delay'; +DROP TABLE vacstat_delay; + -- WAL metrics. A vacuum that removes tuples always emits WAL -- (wal_records > 0, wal_bytes > 0). wal_fpi depends on whether a checkpoint -- happened recently, so it is only checked for being non-negative here; the @@ -123,13 +153,6 @@ SELECT wal_records > 0 AS wal_records, FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_fpi'; DROP TABLE vacstat_fpi; --- wraparound failsafe. A normal vacuum does not engage the wraparound --- failsafe (wraparound_failsafe = 0). The positive path requires reaching the --- failsafe XID age and is covered by a separate TAP test under --- src/test/modules/xid_wraparound. -SELECT wraparound_failsafe = 0 AS wraparound_failsafe - FROM pg_stat_vacuum_tables WHERE relname = 'vacstat_t'; - -- per-index view: the primary key index is processed by the same VACUUM. -- No btree leaf empties out (interleaved deletions), so pages_deleted = 0, -- while every index entry for a removed heap tuple is deleted. The index is @@ -144,6 +167,10 @@ SELECT indexrelname, total_blks_written >= 0 AS total_blks_written, rel_blks_read >= 0 AS rel_blks_read, rel_blks_hit > 0 AS rel_blks_hit, + blk_read_time >= 0 AS blk_read_time, + blk_write_time >= 0 AS blk_write_time, + delay_time >= 0 AS delay_time, + total_time > 0 AS total_time, wal_records > 0 AS wal_records, wal_fpi >= 0 AS wal_fpi, wal_bytes > 0 AS wal_bytes @@ -168,13 +195,17 @@ DROP TABLE vacstat_idxdel; -- per-database aggregate view: no vacuum errors occurred in this database, and -- the vacuums in this database touched pages through the buffer cache --- (db_blks_hit > 0) and emit WAL (wal_records > 0). +-- (db_blks_hit > 0). SELECT errors = 0 AS errors, db_blks_read >= 0 AS db_blks_read, db_blks_hit > 0 AS db_blks_hit, total_blks_dirtied >= 0 AS total_blks_dirtied, total_blks_written >= 0 AS total_blks_written, wraparound_failsafe = 0 AS wraparound_failsafe, + blk_read_time >= 0 AS blk_read_time, + blk_write_time >= 0 AS blk_write_time, + delay_time >= 0 AS delay_time, + total_time > 0 AS total_time, wal_records > 0 AS wal_records, wal_fpi >= 0 AS wal_fpi, wal_bytes > 0 AS wal_bytes -- 2.39.5 (Apple Git-154)