From c420c88f6c6aa326de47954cf552389efa7c3c37 Mon Sep 17 00:00:00 2001 From: Alena Rybakina Date: Mon, 1 Sep 2025 21:42:25 +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 , Andrei Lepikhov , Andrei Zubkov Reviewed-by: Dilip Kumar , Masahiko Sawada , Ilia Evdokimov , jian he , Kirill Reshke , Alexander Korotkov , Jim Nasby , Sami Imseih --- src/backend/access/heap/vacuumlazy.c | 268 ++++++++++++++---- src/backend/catalog/system_views.sql | 32 +++ src/backend/commands/vacuumparallel.c | 14 + src/backend/utils/activity/pgstat.c | 4 + src/backend/utils/activity/pgstat_relation.c | 48 +++- src/backend/utils/adt/pgstatfuncs.c | 133 ++++++++- src/backend/utils/misc/guc_tables.c | 2 +- src/include/catalog/pg_proc.dat | 9 + src/include/commands/vacuum.h | 25 ++ src/include/pgstat.h | 58 +++- .../vacuum-extending-in-repetable-read.out | 4 +- src/test/regress/expected/rules.out | 22 ++ .../expected/vacuum_index_statistics.out | 183 ++++++++++++ src/test/regress/parallel_schedule | 1 + .../regress/sql/vacuum_index_statistics.sql | 151 ++++++++++ 15 files changed, 864 insertions(+), 90 deletions(-) create mode 100644 src/test/regress/expected/vacuum_index_statistics.out create mode 100644 src/test/regress/sql/vacuum_index_statistics.sql diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 980101e9b97..e206aeecc9c 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 */ @@ -410,6 +411,8 @@ typedef struct LVRelState BlockNumber eager_scan_remaining_fails; int32 wraparound_failsafe_count; /* number of emergency vacuums to prevent anti-wraparound shutdown */ + + ExtVacReport extVacReportIdx; } LVRelState; @@ -421,19 +424,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, @@ -555,27 +545,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) /* @@ -584,12 +572,131 @@ 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) +{ + 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; + + 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) +{ + if (!pgstat_track_vacuum_statistics) + return; + + /* 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) +{ + if (!pgstat_track_vacuum_statistics) + return; + + /* 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. @@ -752,7 +859,7 @@ heap_vacuum_rel(Relation rel, const VacuumParams params, ExtVacReport allzero; /* Initialize vacuum statistics */ - memset(&allzero, 0, sizeof(ExtVacReport)); + memset(&extVacReport, 0, sizeof(ExtVacReport)); extVacReport = allzero; verbose = (params.options & VACOPT_VERBOSE) != 0; @@ -799,6 +906,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, @@ -1051,23 +1160,6 @@ heap_vacuum_rel(Relation rel, const VacuumParams params, /* Make generic extended vacuum stats report */ extvac_stats_end(rel, &extVacCounters, &extVacReport); - if(pgstat_track_vacuum_statistics) - { - /* 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. * @@ -1078,13 +1170,34 @@ heap_vacuum_rel(Relation rel, const VacuumParams params, * soon in cases where the failsafe prevented significant amounts of heap * vacuuming. */ - pgstat_report_vacuum(RelationGetRelid(rel), + if(pgstat_track_vacuum_statistics) + { + /* 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(RelationGetRelid(rel), rel->rd_rel->relisshared, Max(vacrel->new_live_tuples, 0), vacrel->recently_dead_tuples + - vacrel->missed_dead_tuples, + vacrel->missed_dead_tuples, starttime, &extVacReport); + + } + else + { + pgstat_report_vacuum(RelationGetRelid(rel), + rel->rd_rel->relisshared, + Max(vacrel->new_live_tuples, 0), + vacrel->recently_dead_tuples + + vacrel->missed_dead_tuples, + starttime, + NULL); + } + pgstat_progress_end_command(); if (instrument) @@ -2776,10 +2889,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. @@ -3189,10 +3312,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 */ @@ -3218,6 +3351,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; @@ -3236,6 +3374,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); @@ -3244,6 +3383,19 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat, istat = vac_bulkdel_one_index(&ivinfo, istat, vacrel->dead_items, vacrel->dead_items_info); + if(pgstat_track_vacuum_statistics) + { + /* 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(RelationGetRelid(indrel), + indrel->rd_rel->relisshared, + 0, 0, 0, &extVacReport); + } + /* Revert to the previous phase information for error traceback */ restore_vacuum_error_info(vacrel, &saved_err_info); pfree(vacrel->indname); @@ -3268,6 +3420,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; @@ -3287,12 +3444,25 @@ 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); + if(pgstat_track_vacuum_statistics) + { + /* 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(RelationGetRelid(indrel), + indrel->rd_rel->relisshared, + 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 6480c3fc92a..eb63be1833a 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1470,3 +1470,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 2b55d9b7c0e..65de45a4447 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,15 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel, RelationGetRelationName(indrel)); } + if(pgstat_track_vacuum_statistics) + { + /* Make extended vacuum stats report for index */ + extvac_stats_end_idx(indrel, istat_res, &extVacCounters, &extVacReport); + pgstat_report_vacuum(RelationGetRelid(indrel), + indrel->rd_rel->relisshared, + 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.c b/src/backend/utils/activity/pgstat.c index a19d4a770b8..2b7058e5c01 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -1162,6 +1162,10 @@ pgstat_build_snapshot(PgStat_Kind statKind) if (p->dropped) continue; + if (statKind != PGSTAT_KIND_INVALID && statKind != p->key.kind) + /* Load stat of specific type, if defined */ + continue; + Assert(pg_atomic_read_u32(&p->refcount) > 0); stats_data = dsa_get_address(pgStatLocal.dsa, p->body); diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index e023926ff05..c6194584b35 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -1016,6 +1016,9 @@ static void pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src, bool accumulate_reltype_specific_info) { + if(!pgstat_track_vacuum_statistics) + return; + dst->total_blks_read += src->total_blks_read; dst->total_blks_hit += src->total_blks_hit; dst->total_blks_dirtied += src->total_blks_dirtied; @@ -1031,20 +1034,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; + } + } } \ No newline at end of file diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index ee461ea378d..defe1990e11 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -2374,18 +2374,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); @@ -2404,6 +2405,116 @@ pg_stat_get_vacuum_tables(PG_FUNCTION_ARGS) Assert(i == PG_STAT_GET_VACUUM_TABLES_STATS_COLS); + /* 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; + ExtVacReport allzero; + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_VACUUM_INDEX_STATS_COLS); + + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "relid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_ blks_read", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_blks_hit", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_blks_dirtied", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_blks_written", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "rel_blks_read", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "rel_blks_hit", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "pages_deleted", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "tuples_deleted", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wal_records", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wal_fpi", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "wal_bytes", + NUMERICOID, -1, 0); + + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "blk_read_time", + FLOAT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "blk_write_time", + FLOAT8OID, -1, 0); + + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "delay_time", + FLOAT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "total_time", + FLOAT8OID, -1, 0); + + Assert(i == PG_STAT_GET_VACUUM_INDEX_STATS_COLS); + + BlessTupleDesc(tupdesc); + + tabentry = pgstat_fetch_stat_tabentry(relid); + + if (tabentry == NULL) + { + /* If the subscription is not found, initialise its stats */ + memset(&allzero, 0, sizeof(ExtVacReport)); + extvacuum = &allzero; + } + 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))); } \ No newline at end of file diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 5af593aa1a7..ac6427cf1e9 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -1570,7 +1570,7 @@ struct config_bool ConfigureNamesBool[] = { {"track_vacuum_statistics", PGC_SUSET, STATS_CUMULATIVE, - gettext_noop("Collects vacuum statistics for table relations."), + gettext_noop("Collects vacuum statistics for relations."), NULL }, &pgstat_track_vacuum_statistics, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 687d51c2a6a..2fbd001178e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12594,4 +12594,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 4d05e1a0fac..dcc542750b8 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 @@ -295,6 +296,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; @@ -408,4 +429,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 4fd114e9a5f..aec25ad2262 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -111,11 +111,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 @@ -144,18 +152,44 @@ typedef struct ExtVacReport 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 */ + + 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/regress/expected/rules.out b/src/test/regress/expected/rules.out index 349e7deba01..fdd5341bdfc 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2293,6 +2293,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, diff --git a/src/test/regress/expected/vacuum_index_statistics.out b/src/test/regress/expected/vacuum_index_statistics.out new file mode 100644 index 00000000000..e00a0fc683c --- /dev/null +++ b/src/test/regress/expected/vacuum_index_statistics.out @@ -0,0 +1,183 @@ +-- +-- Test cumulative vacuum stats system +-- +-- Check the wall statistics collected during vacuum operation: +-- number of frozen and visible pages set by vacuum; +-- number of frozen and visible pages removed by backend. +-- Statistic wal_fpi is not displayed in this test because its behavior is unstable. +-- +-- conditio sine qua non +SHOW track_counts; -- must be on + track_counts +-------------- + on +(1 row) + +\set sample_size 10000 +-- not enabled by default, but we want to test it... +SET track_functions TO 'all'; +-- Test that vacuum statistics will be empty when parameter is off. +SET track_vacuum_statistics TO 'off'; +CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +ANALYZE vestat; +DELETE FROM vestat WHERE x % 2 = 0; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat; +-- Must be empty. +SELECT * +FROM pg_stat_vacuum_indexes vt +WHERE vt.relname = 'vestat'; + relid | schemaname | relname | 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 +-------+------------+---------+-----------------+----------------+--------------------+--------------------+---------------+--------------+---------------+----------------+-------------+---------+-----------+---------------+----------------+------------+------------ +(0 rows) + +RESET track_vacuum_statistics; +DROP TABLE vestat CASCADE; +SHOW track_vacuum_statistics; -- must be on + track_vacuum_statistics +------------------------- + on +(1 row) + +-- ensure pending stats are flushed +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +\set sample_size 10000 +SET vacuum_freeze_min_age = 0; +SET vacuum_freeze_table_age = 0; +--SET stats_fetch_consistency = snapshot; +CREATE TABLE vestat (x int primary key) WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +ANALYZE vestat; +SELECT oid AS ioid from pg_class where relname = 'vestat_pkey' \gset +DELETE FROM vestat WHERE x % 2 = 0; +-- Before the first vacuum execution extended stats view is empty. +SELECT vt.relname,relpages,pages_deleted,tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; + relname | relpages | pages_deleted | tuples_deleted +-------------+----------+---------------+---------------- + vestat_pkey | 30 | 0 | 0 +(1 row) + +SELECT relpages AS irp +FROM pg_class c +WHERE relname = 'vestat_pkey' \gset +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; +-- The table and index extended vacuum statistics should show us that +-- vacuum frozed pages and clean up pages, but pages_removed stayed the same +-- because of not full table have cleaned up +SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted = 0 AS pages_deleted,tuples_deleted > 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; + relname | relpages | pages_deleted | tuples_deleted +-------------+----------+---------------+---------------- + vestat_pkey | t | t | t +(1 row) + +SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset +-- Store WAL advances into variables +SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset +-- Look into WAL records deltas. +SELECT wal_records > 0 AS diWR, wal_bytes > 0 AS diWB +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey'; + diwr | diwb +------+------ + t | t +(1 row) + +DELETE FROM vestat;; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; +-- pages_removed must be increased +SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd > 0 AS pages_deleted,tuples_deleted-:itd > 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; + relname | relpages | pages_deleted | tuples_deleted +-------------+----------+---------------+---------------- + vestat_pkey | t | t | t +(1 row) + +SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset +-- Store WAL advances into variables +SELECT wal_records-:iwr AS diwr, wal_bytes-:iwb AS diwb, wal_fpi-:ifpi AS difpi +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset +-- WAL advance should be detected. +SELECT :diwr > 0 AS diWR, :diwb > 0 AS diWB; + diwr | diwb +------+------ + t | t +(1 row) + +-- Store WAL advances into variables +SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +DELETE FROM vestat WHERE x % 2 = 0; +-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics +-- are detected here. +VACUUM FULL vestat; +-- It is necessary to check the wal statistics +CHECKPOINT; +-- Store WAL advances into variables +SELECT wal_records-:iwr AS diwr2, wal_bytes-:iwb AS diwb2, wal_fpi-:ifpi AS difpi2 +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset +-- WAL and other statistics advance should not be detected. +SELECT :diwr2=0 AS diWR, :difpi2=0 AS iFPI, :diwb2=0 AS diWB; + diwr | ifpi | diwb +------+------+------ + t | t | t +(1 row) + +SELECT vt.relname,relpages-:irp < 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; + relname | relpages | pages_deleted | tuples_deleted +-------------+----------+---------------+---------------- + vestat_pkey | t | t | t +(1 row) + +SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset +-- Store WAL advances into variables +SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset +DELETE FROM vestat; +TRUNCATE vestat; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; +-- Store WAL advances into variables after removing all tuples from the table +SELECT wal_records-:iwr AS diwr3, wal_bytes-:iwb AS diwb3, wal_fpi-:ifpi AS difpi3 +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset +--There are nothing changed +SELECT :diwr3=0 AS diWR, :difpi3=0 AS iFPI, :diwb3=0 AS diWB; + diwr | ifpi | diwb +------+------+------ + t | t | t +(1 row) + +-- +-- Now, the table and index is compressed into zero number of pages. Check it +-- in vacuum extended statistics. +-- The pages_frozen, pages_scanned values shouldn't be changed +-- +SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; + relname | relpages | pages_deleted | tuples_deleted +-------------+----------+---------------+---------------- + vestat_pkey | f | t | t +(1 row) + +DROP TABLE vestat; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index cd779ab8eca..4eb03353104 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -144,4 +144,5 @@ test: tablespace # ---------- # Check vacuum statistics # ---------- +test: vacuum_index_statistics test: vacuum_tables_statistics \ No newline at end of file diff --git a/src/test/regress/sql/vacuum_index_statistics.sql b/src/test/regress/sql/vacuum_index_statistics.sql new file mode 100644 index 00000000000..ae146e1d23f --- /dev/null +++ b/src/test/regress/sql/vacuum_index_statistics.sql @@ -0,0 +1,151 @@ +-- +-- Test cumulative vacuum stats system +-- +-- Check the wall statistics collected during vacuum operation: +-- number of frozen and visible pages set by vacuum; +-- number of frozen and visible pages removed by backend. +-- Statistic wal_fpi is not displayed in this test because its behavior is unstable. +-- +-- conditio sine qua non +SHOW track_counts; -- must be on + +\set sample_size 10000 + +-- not enabled by default, but we want to test it... +SET track_functions TO 'all'; + +-- Test that vacuum statistics will be empty when parameter is off. +SET track_vacuum_statistics TO 'off'; + +CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +ANALYZE vestat; + +DELETE FROM vestat WHERE x % 2 = 0; + +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) vestat; + +-- Must be empty. +SELECT * +FROM pg_stat_vacuum_indexes vt +WHERE vt.relname = 'vestat'; + +RESET track_vacuum_statistics; +DROP TABLE vestat CASCADE; + +SHOW track_vacuum_statistics; -- must be on + +-- ensure pending stats are flushed +SELECT pg_stat_force_next_flush(); + +\set sample_size 10000 +SET vacuum_freeze_min_age = 0; +SET vacuum_freeze_table_age = 0; +--SET stats_fetch_consistency = snapshot; +CREATE TABLE vestat (x int primary key) WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +ANALYZE vestat; + +SELECT oid AS ioid from pg_class where relname = 'vestat_pkey' \gset + +DELETE FROM vestat WHERE x % 2 = 0; +-- Before the first vacuum execution extended stats view is empty. +SELECT vt.relname,relpages,pages_deleted,tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; +SELECT relpages AS irp +FROM pg_class c +WHERE relname = 'vestat_pkey' \gset + +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; + +-- The table and index extended vacuum statistics should show us that +-- vacuum frozed pages and clean up pages, but pages_removed stayed the same +-- because of not full table have cleaned up +SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted = 0 AS pages_deleted,tuples_deleted > 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; +SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset + +-- Store WAL advances into variables +SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset + +-- Look into WAL records deltas. +SELECT wal_records > 0 AS diWR, wal_bytes > 0 AS diWB +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey'; + +DELETE FROM vestat;; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; + +-- pages_removed must be increased +SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd > 0 AS pages_deleted,tuples_deleted-:itd > 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; +SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset + +-- Store WAL advances into variables +SELECT wal_records-:iwr AS diwr, wal_bytes-:iwb AS diwb, wal_fpi-:ifpi AS difpi +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset + +-- WAL advance should be detected. +SELECT :diwr > 0 AS diWR, :diwb > 0 AS diWB; + +-- Store WAL advances into variables +SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset + +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +DELETE FROM vestat WHERE x % 2 = 0; +-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics +-- are detected here. +VACUUM FULL vestat; +-- It is necessary to check the wal statistics +CHECKPOINT; + +-- Store WAL advances into variables +SELECT wal_records-:iwr AS diwr2, wal_bytes-:iwb AS diwb2, wal_fpi-:ifpi AS difpi2 +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset + +-- WAL and other statistics advance should not be detected. +SELECT :diwr2=0 AS diWR, :difpi2=0 AS iFPI, :diwb2=0 AS diWB; + +SELECT vt.relname,relpages-:irp < 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; +SELECT vt.relname,relpages AS irp,pages_deleted AS ipd,tuples_deleted AS itd +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid \gset + +-- Store WAL advances into variables +SELECT wal_records AS iwr,wal_bytes AS iwb,wal_fpi AS ifpi FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset + +DELETE FROM vestat; +TRUNCATE vestat; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP ON) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; + +-- Store WAL advances into variables after removing all tuples from the table +SELECT wal_records-:iwr AS diwr3, wal_bytes-:iwb AS diwb3, wal_fpi-:ifpi AS difpi3 +FROM pg_stat_vacuum_indexes WHERE relname = 'vestat_pkey' \gset + +--There are nothing changed +SELECT :diwr3=0 AS diWR, :difpi3=0 AS iFPI, :diwb3=0 AS diWB; + +-- +-- Now, the table and index is compressed into zero number of pages. Check it +-- in vacuum extended statistics. +-- The pages_frozen, pages_scanned values shouldn't be changed +-- +SELECT vt.relname,relpages-:irp = 0 AS relpages,pages_deleted-:ipd = 0 AS pages_deleted,tuples_deleted-:itd = 0 AS tuples_deleted +FROM pg_stat_vacuum_indexes vt, pg_class c +WHERE vt.relname = 'vestat_pkey' AND vt.relid = c.oid; + +DROP TABLE vestat; -- 2.34.1