From ce083bea162a5a46136454f3242e68376bb69d42 Mon Sep 17 00:00:00 2001 From: Alena Rybakina Date: Wed, 22 Jul 2026 22:57:35 +0300 Subject: [PATCH v42 6/9] ext_vacuum_statistics: page counters for tables and indexes. Add the page-level counters to the vacuum report and the extension views: * pages_scanned - heap pages inspected by the vacuum; * pages_removed - pages by which the table storage was truncated at the end of the vacuum; * missed_dead_pages - pages containing fully dead tuples that could not be pruned because the cleanup lock on the page was not acquired; * pages_deleted (indexes only) - index pages marked deleted by the vacuum, derived per bulkdelete/cleanup pass, including passes executed in parallel workers. --- .../vacuum-extending-in-repetable-read.out | 22 ++++++------- .../ext_vacuum_statistics--1.0.sql | 12 +++++-- .../vacuum-extending-in-repetable-read.spec | 2 +- .../t/054_vacuum_extending_gucs_test.pl | 2 +- .../ext_vacuum_statistics/vacuum_statistics.c | 15 +++++++-- doc/src/sgml/extvacuumstatistics.sgml | 32 +++++++++++++++++++ src/backend/access/heap/vacuumlazy.c | 19 +++++++++++ src/backend/commands/vacuumparallel.c | 8 +++++ src/include/pgstat.h | 7 ++++ 9 files changed, 102 insertions(+), 17 deletions(-) diff --git a/contrib/ext_vacuum_statistics/expected/vacuum-extending-in-repetable-read.out b/contrib/ext_vacuum_statistics/expected/vacuum-extending-in-repetable-read.out index f8962fad325..6b381f9d232 100644 --- a/contrib/ext_vacuum_statistics/expected/vacuum-extending-in-repetable-read.out +++ b/contrib/ext_vacuum_statistics/expected/vacuum-extending-in-repetable-read.out @@ -5,12 +5,12 @@ starting permutation: s2_insert s2_print_vacuum_stats_table s1_begin_repeatable_ 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.tuples_frozen + vt.relname, vt.tuples_deleted, vt.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, vt.tuples_frozen FROM ext_vacuum_statistics.pg_stats_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|tuples_frozen --------+--------------+--------------------+------------------+------------- +relname|tuples_deleted|recently_dead_tuples|missed_dead_tuples|missed_dead_pages|tuples_frozen +-------+--------------+--------------------+------------------+-----------------+------------- (0 rows) step s1_begin_repeatable_read: @@ -27,13 +27,13 @@ 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.tuples_frozen + vt.relname, vt.tuples_deleted, vt.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, vt.tuples_frozen FROM ext_vacuum_statistics.pg_stats_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|tuples_frozen ---------------------------+--------------+--------------------+------------------+------------- -test_vacuum_stat_isolation| 0| 100| 0| 0 +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; @@ -41,12 +41,12 @@ 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.tuples_frozen + vt.relname, vt.tuples_deleted, vt.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, vt.tuples_frozen FROM ext_vacuum_statistics.pg_stats_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|tuples_frozen ---------------------------+--------------+--------------------+------------------+------------- -test_vacuum_stat_isolation| 100| 100| 0| 101 +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/contrib/ext_vacuum_statistics/ext_vacuum_statistics--1.0.sql b/contrib/ext_vacuum_statistics/ext_vacuum_statistics--1.0.sql index d2408c3b920..0faca18a463 100644 --- a/contrib/ext_vacuum_statistics/ext_vacuum_statistics--1.0.sql +++ b/contrib/ext_vacuum_statistics/ext_vacuum_statistics--1.0.sql @@ -45,8 +45,11 @@ CREATE OR REPLACE FUNCTION ext_vacuum_statistics.pg_stats_get_vacuum_tables( IN reloid oid, OUT relid oid, OUT tuples_deleted bigint, + OUT pages_scanned bigint, + OUT pages_removed bigint, OUT tuples_frozen bigint, OUT recently_dead_tuples bigint, + OUT missed_dead_pages bigint, OUT missed_dead_tuples bigint ) RETURNS SETOF record @@ -58,7 +61,8 @@ CREATE OR REPLACE FUNCTION ext_vacuum_statistics.pg_stats_get_vacuum_indexes( IN dboid oid, IN reloid oid, OUT relid oid, - OUT tuples_deleted bigint + OUT tuples_deleted bigint, + OUT pages_deleted bigint ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pg_stats_get_vacuum_indexes' @@ -72,8 +76,11 @@ SELECT rel.relname AS relname, db.datname AS dbname, stats.tuples_deleted, + stats.pages_scanned, + stats.pages_removed, stats.tuples_frozen, stats.recently_dead_tuples, + stats.missed_dead_pages, stats.missed_dead_tuples FROM pg_database db, pg_class rel, @@ -94,7 +101,8 @@ SELECT ns.nspname AS schema, rel.relname AS indexrelname, db.datname AS dbname, - stats.tuples_deleted + stats.tuples_deleted, + stats.pages_deleted FROM pg_database db, pg_class rel, pg_namespace ns, diff --git a/contrib/ext_vacuum_statistics/specs/vacuum-extending-in-repetable-read.spec b/contrib/ext_vacuum_statistics/specs/vacuum-extending-in-repetable-read.spec index 3001b56ac2b..4891e248cca 100644 --- a/contrib/ext_vacuum_statistics/specs/vacuum-extending-in-repetable-read.spec +++ b/contrib/ext_vacuum_statistics/specs/vacuum-extending-in-repetable-read.spec @@ -40,7 +40,7 @@ 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.tuples_frozen + vt.relname, vt.tuples_deleted, vt.recently_dead_tuples, vt.missed_dead_tuples, vt.missed_dead_pages, vt.tuples_frozen FROM ext_vacuum_statistics.pg_stats_vacuum_tables vt, pg_class c WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid; } diff --git a/contrib/ext_vacuum_statistics/t/054_vacuum_extending_gucs_test.pl b/contrib/ext_vacuum_statistics/t/054_vacuum_extending_gucs_test.pl index e44d54d040f..2632191e188 100644 --- a/contrib/ext_vacuum_statistics/t/054_vacuum_extending_gucs_test.pl +++ b/contrib/ext_vacuum_statistics/t/054_vacuum_extending_gucs_test.pl @@ -88,7 +88,7 @@ subtest 'vacuum_statistics.enabled' => sub { my $sums = $node->safe_psql($dbname, q{ SELECT COALESCE(SUM(tuples_deleted), 0) - + COALESCE(SUM(tuples_frozen), 0) + + COALESCE(SUM(pages_scanned), 0) FROM ext_vacuum_statistics.pg_stats_vacuum_tables WHERE relname = 'guc_test' }); diff --git a/contrib/ext_vacuum_statistics/vacuum_statistics.c b/contrib/ext_vacuum_statistics/vacuum_statistics.c index 8d0d942d3ad..0570fc5c071 100644 --- a/contrib/ext_vacuum_statistics/vacuum_statistics.c +++ b/contrib/ext_vacuum_statistics/vacuum_statistics.c @@ -84,10 +84,17 @@ pgstat_accumulate_extvac_stats(PgStat_VacuumRelationCounts * dst, if (dst->type == PGSTAT_EXTVAC_TABLE) { + dst->table.pages_scanned += src->table.pages_scanned; + dst->table.pages_removed += src->table.pages_removed; dst->table.tuples_frozen += src->table.tuples_frozen; dst->table.recently_dead_tuples += src->table.recently_dead_tuples; + 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; + } } void @@ -218,8 +225,8 @@ extvac_shared_memory_size(PG_FUNCTION_ARGS) /* * Output vacuum statistics (tables or indexes). */ -#define EXTVAC_HEAP_STAT_COLS 5 -#define EXTVAC_IDX_STAT_COLS 2 +#define EXTVAC_HEAP_STAT_COLS 8 +#define EXTVAC_IDX_STAT_COLS 3 #define EXTVAC_MAX_STAT_COLS Max(EXTVAC_HEAP_STAT_COLS, EXTVAC_IDX_STAT_COLS) static void @@ -236,13 +243,17 @@ tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore, if (vacuum_ext->type == PGSTAT_EXTVAC_TABLE) { values[i++] = Int64GetDatum(vacuum_ext->common.tuples_deleted); + values[i++] = Int64GetDatum(vacuum_ext->table.pages_scanned); + values[i++] = Int64GetDatum(vacuum_ext->table.pages_removed); values[i++] = Int64GetDatum(vacuum_ext->table.tuples_frozen); values[i++] = Int64GetDatum(vacuum_ext->table.recently_dead_tuples); + values[i++] = Int64GetDatum(vacuum_ext->table.missed_dead_pages); values[i++] = Int64GetDatum(vacuum_ext->table.missed_dead_tuples); } else if (vacuum_ext->type == PGSTAT_EXTVAC_INDEX) { values[i++] = Int64GetDatum(vacuum_ext->common.tuples_deleted); + values[i++] = Int64GetDatum(vacuum_ext->index.pages_deleted); } Assert(i == ((vacuum_ext->type == PGSTAT_EXTVAC_TABLE) ? EXTVAC_HEAP_STAT_COLS : EXTVAC_IDX_STAT_COLS)); diff --git a/doc/src/sgml/extvacuumstatistics.sgml b/doc/src/sgml/extvacuumstatistics.sgml index ff91f360474..5645b58b640 100644 --- a/doc/src/sgml/extvacuumstatistics.sgml +++ b/doc/src/sgml/extvacuumstatistics.sgml @@ -105,6 +105,22 @@ Number of dead tuples vacuum operations deleted from this table + + + pages_scanned int8 + + + Number of pages examined by vacuum operations + + + + + pages_removed int8 + + + Number of pages removed from physical storage by vacuum operations + + tuples_frozen int8 @@ -121,6 +137,14 @@ Number of dead tuples left due to visibility in transactions + + + missed_dead_pages int8 + + + Number of pages that had at least one missed dead tuple + + missed_dead_tuples int8 @@ -203,6 +227,14 @@ of the parent table + + + pages_deleted int8 + + + Number of index pages marked deleted by vacuum operations + + diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 71d1d97ac7f..e27df6a6d0b 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -504,10 +504,13 @@ static void accumulate_heap_vacuum_statistics(LVRelState *vacrel, PgStat_VacuumRelationCounts * extVacStats) { extVacStats->type = PGSTAT_EXTVAC_TABLE; + extVacStats->table.pages_scanned = vacrel->scanned_pages; + extVacStats->table.pages_removed = vacrel->removed_pages; 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.missed_dead_tuples = vacrel->missed_dead_tuples; + extVacStats->table.missed_dead_pages = vacrel->missed_dead_pages; /* * The wraparound failsafe is reported as a per-relation flag (0/1): did @@ -3081,6 +3084,7 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat, TimestampTz istarttime = GetCurrentTimestamp(); double startdelaytime = VacuumDelayTime; double prev_tuples_removed = 0; + BlockNumber prev_pages_deleted = 0; PgStat_VacuumRelationCounts extVacReport; /* @@ -3088,7 +3092,10 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat, * several times per vacuum, and the report below covers this pass only. */ if (istat != NULL) + { prev_tuples_removed = istat->tuples_removed; + prev_pages_deleted = istat->pages_deleted; + } ivinfo.index = indrel; ivinfo.heaprel = vacrel->rel; ivinfo.analyze_only = false; @@ -3128,8 +3135,12 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat, memset(&extVacReport, 0, sizeof(extVacReport)); extVacReport.type = PGSTAT_EXTVAC_INDEX; if (istat != NULL) + { extVacReport.common.tuples_deleted = istat->tuples_removed - prev_tuples_removed; + extVacReport.index.pages_deleted = + istat->pages_deleted - prev_pages_deleted; + } pgstat_report_vacuum_ext(indrel, -1, -1, 0, 0, false, &extVacReport); } @@ -3160,6 +3171,7 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat, TimestampTz istarttime = GetCurrentTimestamp(); double startdelaytime = VacuumDelayTime; double prev_tuples_removed = 0; + BlockNumber prev_pages_deleted = 0; PgStat_VacuumRelationCounts extVacReport; /* @@ -3167,7 +3179,10 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat, * several times per vacuum, and the report below covers this pass only. */ if (istat != NULL) + { prev_tuples_removed = istat->tuples_removed; + prev_pages_deleted = istat->pages_deleted; + } ivinfo.index = indrel; ivinfo.heaprel = vacrel->rel; ivinfo.analyze_only = false; @@ -3206,8 +3221,12 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat, memset(&extVacReport, 0, sizeof(extVacReport)); extVacReport.type = PGSTAT_EXTVAC_INDEX; if (istat != NULL) + { extVacReport.common.tuples_deleted = istat->tuples_removed - prev_tuples_removed; + extVacReport.index.pages_deleted = + istat->pages_deleted - prev_pages_deleted; + } pgstat_report_vacuum_ext(indrel, -1, -1, 0, 0, false, &extVacReport); } diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index 958bb5bd8a3..2f3415fbfcb 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -1081,6 +1081,7 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel, TimestampTz istarttime = GetCurrentTimestamp(); double startdelaytime = VacuumDelayTime; double prev_tuples_removed = 0; + BlockNumber prev_pages_deleted = 0; PgStat_VacuumRelationCounts extVacReport; /* @@ -1095,7 +1096,10 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel, * several times per vacuum, and the report below covers this pass only. */ if (istat != NULL) + { prev_tuples_removed = istat->tuples_removed; + prev_pages_deleted = istat->pages_deleted; + } ivinfo.index = indrel; ivinfo.heaprel = pvs->heaprel; ivinfo.analyze_only = false; @@ -1129,8 +1133,12 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel, memset(&extVacReport, 0, sizeof(extVacReport)); extVacReport.type = PGSTAT_EXTVAC_INDEX; if (istat_res != NULL) + { extVacReport.common.tuples_deleted = istat_res->tuples_removed - prev_tuples_removed; + extVacReport.index.pages_deleted = + istat_res->pages_deleted - prev_pages_deleted; + } pgstat_report_vacuum_ext(indrel, -1, -1, 0, 0, false, &extVacReport); } diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b17670610bc..9ff55dfe94b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -121,7 +121,14 @@ typedef struct PgStat_VacuumRelationCounts int64 tuples_frozen; int64 recently_dead_tuples; int64 missed_dead_tuples; + int64 pages_scanned; + int64 pages_removed; + int64 missed_dead_pages; } table; + struct + { + int64 pages_deleted; + } index; }; } PgStat_VacuumRelationCounts; -- 2.39.5 (Apple Git-154)