From 48d74c99d308dda2d282db64d8c8d973eb31ecce Mon Sep 17 00:00:00 2001 From: Alena Rybakina Date: Tue, 11 Jun 2024 09:49:15 +0300 Subject: [PATCH 1/3] Machinery for grabbing an extended vacuum statistics on heap relations. Value of total_blks_hit, total_blks_read, total_blks_dirtied are number of hitted, missed and dirtied pages in shared buffers during a vacuum operation respectively. total_blks_dirtied means 'dirtied only by this action'. So, if this page was dirty before the vacuum operation, it doesn't count this page as 'dirtied'. The tuples_deleted parameter is the number of tuples cleaned up by the vacuum operation. The delay_time value means total vacuum sleep time in vacuum delay point. The pages_removed value is the number of pages by which the physical data storage of the relation was reduced. The value of pages_deleted parameter is the number of freed pages in the table (file size may not have changed). Interruptions number of (auto)vacuum process during vacuuming of a relation. We report from the vacuum_error_callback routine. So we can log all ERROR reports. In the case of autovacuum we can report SIGINT signals too. It maybe dangerous to make such complex task (send) in an error callback - we can catch ERROR in ERROR problem. But it looks like we have so small chance to stuck into this problem. So, let's try to use. This parameter relates to a problem, covered by b19e4250. Tracking of IO during an (auto)vacuum operation. Introduced variables blk_read_time and blk_write_time tracks only access to buffer pages and flushing them to disk. Reading operation is trivial, but writing measurement technique is not obvious. So, during a vacuum writing time can be zero incremented because no any flushing operations were performed. System time and user time are parameters that describes how much time a vacuum operation has spent in executing of code in user space and kernel space accordingly. Also, accumulate total time of a vacuum that is a diff between timestamps in start and finish points in the vacuum code. Remember about idle time, when vacuum waited for IO and locks, so total time isn't equal a sum of user and system time, but no less. pages_frozen - number of pages that are marked as frozen in vm during vacuum. This parameter is incremented if page is marked as all-frozen. pages_all_visible - number of pages that are marked as all-visible in vm during vacuum. Authors: Alena Rybakina , Andrei Lepikhov , Andrei Zubkov --- src/backend/access/heap/vacuumlazy.c | 161 ++++++++++++++- src/backend/access/heap/visibilitymap.c | 13 ++ src/backend/catalog/system_views.sql | 54 +++++ src/backend/commands/vacuum.c | 4 + src/backend/commands/vacuumparallel.c | 1 + src/backend/utils/activity/pgstat.c | 94 +++++---- src/backend/utils/activity/pgstat_relation.c | 35 +++- src/backend/utils/adt/pgstatfuncs.c | 186 ++++++++++++++++++ src/backend/utils/error/elog.c | 13 ++ src/include/catalog/pg_proc.dat | 10 +- src/include/commands/vacuum.h | 1 + src/include/pgstat.h | 84 +++++++- src/include/utils/elog.h | 2 +- src/include/utils/pgstat_internal.h | 36 +++- .../vacuum-extending-in-repetable-read.out | 53 +++++ src/test/isolation/isolation_schedule | 1 + .../vacuum-extending-in-repetable-read.spec | 51 +++++ src/test/regress/expected/opr_sanity.out | 7 +- src/test/regress/expected/rules.out | 34 ++++ .../expected/vacuum_tables_statistics.out | 164 +++++++++++++++ src/test/regress/parallel_schedule | 5 + .../regress/sql/vacuum_tables_statistics.sql | 135 +++++++++++++ 22 files changed, 1098 insertions(+), 46 deletions(-) create mode 100644 src/test/isolation/expected/vacuum-extending-in-repetable-read.out create mode 100644 src/test/isolation/specs/vacuum-extending-in-repetable-read.spec create mode 100644 src/test/regress/expected/vacuum_tables_statistics.out create mode 100644 src/test/regress/sql/vacuum_tables_statistics.sql diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 8145ea8fc3..51459e2f1e 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -168,6 +168,9 @@ typedef struct LVRelState char *dbname; char *relnamespace; char *relname; + Oid reloid; + Oid indoid; + char *indname; /* Current index name */ BlockNumber blkno; /* used only for heap operations */ OffsetNumber offnum; /* used only for heap operations */ @@ -194,6 +197,8 @@ typedef struct LVRelState BlockNumber lpdead_item_pages; /* # pages with LP_DEAD items */ BlockNumber missed_dead_pages; /* # pages with missed dead tuples */ BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */ + BlockNumber set_frozen_pages; /* pages are marked as frozen in vm during vacuum */ + BlockNumber set_all_visible_pages; /* pages are marked as all-visible in vm during vacuum */ /* Statistics output by us, for table */ double new_rel_tuples; /* new estimated total # of tuples */ @@ -226,6 +231,22 @@ typedef struct LVSavedErrInfo VacErrPhase phase; } LVSavedErrInfo; +/* + * Cut-off values of parameters which changes implicitly during a vacuum + * process. + * Vacuum can't control their values, so we should store them before and after + * the processing. + */ +typedef struct LVExtStatCounters +{ + TimestampTz time; + PGRUsage ru; + WalUsage walusage; + BufferUsage bufusage; + double VacuumDelayTime; + PgStat_Counter blocks_fetched; + PgStat_Counter blocks_hit; +} LVExtStatCounters; /* non-export function prototypes */ static void lazy_scan_heap(LVRelState *vacrel); @@ -279,6 +300,115 @@ static void update_vacuum_error_info(LVRelState *vacrel, static void restore_vacuum_error_info(LVRelState *vacrel, const LVSavedErrInfo *saved_vacrel); +/* ---------- + * extvac_stats_start() - + * + * Save cut-off values of extended vacuum counters before start of a relation + * processing. + * ---------- + */ +static void +extvac_stats_start(Relation rel, LVExtStatCounters *counters) +{ + TimestampTz starttime; + PGRUsage ru0; + + memset(counters, 0, sizeof(LVExtStatCounters)); + + pg_rusage_init(&ru0); + starttime = GetCurrentTimestamp(); + + counters->ru = ru0; + counters->time = starttime; + counters->walusage = pgWalUsage; + counters->bufusage = pgBufferUsage; + counters->VacuumDelayTime = VacuumDelayTime; + counters->blocks_fetched = 0; + counters->blocks_hit = 0; + + if (!rel->pgstat_info || !pgstat_track_counts) + /* + * if something goes wrong or an user doesn't want to track a database + * activity - just suppress it. + */ + return; + + counters->blocks_fetched = rel->pgstat_info->counts.blocks_fetched; + counters->blocks_hit = rel->pgstat_info->counts.blocks_hit; +} + +/* ---------- + * extvac_stats_end() - + * + * Called to finish an extended vacuum statistic gathering and form a report. + * ---------- + */ +static void +extvac_stats_end(Relation rel, LVExtStatCounters *counters, + ExtVacReport *report) +{ + WalUsage walusage; + BufferUsage bufusage; + TimestampTz endtime; + long secs; + int usecs; + PGRUsage ru1; + + /* Calculate diffs of global stat parameters on WAL and buffer usage. */ + memset(&walusage, 0, sizeof(WalUsage)); + WalUsageAccumDiff(&walusage, &pgWalUsage, &counters->walusage); + + memset(&bufusage, 0, sizeof(BufferUsage)); + BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &counters->bufusage); + + endtime = GetCurrentTimestamp(); + TimestampDifference(counters->time, endtime, &secs, &usecs); + + memset(report, 0, sizeof(ExtVacReport)); + + /* + * Fill additional statistics on a vacuum processing operation. + */ + report->total_blks_read = bufusage.local_blks_read + bufusage.shared_blks_read; + report->total_blks_hit = bufusage.local_blks_hit + bufusage.shared_blks_hit; + report->total_blks_dirtied = bufusage.local_blks_dirtied + bufusage.shared_blks_dirtied; + report->total_blks_written = bufusage.shared_blks_written; + + report->wal_records = walusage.wal_records; + report->wal_fpi = walusage.wal_fpi; + report->wal_bytes = walusage.wal_bytes; + + report->blk_read_time = INSTR_TIME_GET_MILLISEC(bufusage.local_blk_read_time); + report->blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage.shared_blk_read_time); + report->blk_write_time = INSTR_TIME_GET_MILLISEC(bufusage.local_blk_write_time); + report->blk_write_time = INSTR_TIME_GET_MILLISEC(bufusage.shared_blk_write_time); + report->delay_time = VacuumDelayTime - counters->VacuumDelayTime; + + /* + * Get difference of a system time and user time values in milliseconds. + * Use floating point representation to show tails of time diffs. + */ + pg_rusage_init(&ru1); + report->system_time = + (ru1.ru.ru_stime.tv_sec - counters->ru.ru.ru_stime.tv_sec) * 1000. + + (ru1.ru.ru_stime.tv_usec - counters->ru.ru.ru_stime.tv_usec) * 0.001; + report->user_time = + (ru1.ru.ru_utime.tv_sec - counters->ru.ru.ru_utime.tv_sec) * 1000. + + (ru1.ru.ru_utime.tv_usec - counters->ru.ru.ru_utime.tv_usec) * 0.001; + report->total_time = secs * 1000. + usecs / 1000.; + + if (!rel->pgstat_info || !pgstat_track_counts) + /* + * if something goes wrong or an user doesn't want to track a database + * activity - just suppress it. + */ + return; + + report->blks_fetched = + rel->pgstat_info->counts.blocks_fetched - counters->blocks_fetched; + report->blks_hit = + rel->pgstat_info->counts.blocks_hit - counters->blocks_hit; +} /* * heap_vacuum_rel() -- perform VACUUM for one heap relation @@ -311,6 +441,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, WalUsage startwalusage = pgWalUsage; BufferUsage startbufferusage = pgBufferUsage; ErrorContextCallback errcallback; + LVExtStatCounters extVacCounters; + ExtVacReport extVacReport; char **indnames = NULL; verbose = (params->options & VACOPT_VERBOSE) != 0; @@ -329,7 +461,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM, RelationGetRelid(rel)); - + extvac_stats_start(rel, &extVacCounters); /* * Setup error traceback support for ereport() first. The idea is to set * up an error context callback to display additional information on any @@ -346,6 +478,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->dbname = get_database_name(MyDatabaseId); vacrel->relnamespace = get_namespace_name(RelationGetNamespace(rel)); vacrel->relname = pstrdup(RelationGetRelationName(rel)); + vacrel->reloid = RelationGetRelid(rel); vacrel->indname = NULL; vacrel->phase = VACUUM_ERRCB_PHASE_UNKNOWN; vacrel->verbose = verbose; @@ -413,6 +546,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->lpdead_item_pages = 0; vacrel->missed_dead_pages = 0; vacrel->nonempty_pages = 0; + vacrel->set_frozen_pages = 0; + vacrel->set_all_visible_pages = 0; /* dead_items_alloc allocates vacrel->dead_items later on */ /* Allocate/initialize output statistics state */ @@ -574,6 +709,19 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->NewRelfrozenXid, vacrel->NewRelminMxid, &frozenxid_updated, &minmulti_updated, false); + /* Make generic extended vacuum stats report */ + extvac_stats_end(rel, &extVacCounters, &extVacReport); + + /* Fill heap-specific extended stats fields */ + extVacReport.pages_scanned = vacrel->scanned_pages; + extVacReport.pages_removed = vacrel->removed_pages; + extVacReport.pages_frozen = vacrel->set_frozen_pages; + extVacReport.pages_all_visible = vacrel->set_all_visible_pages; + extVacReport.tuples_deleted = vacrel->tuples_deleted; + extVacReport.tuples_frozen = vacrel->tuples_frozen; + extVacReport.dead_tuples = vacrel->recently_dead_tuples + vacrel->missed_dead_tuples; + extVacReport.index_vacuum_count = vacrel->num_index_scans; + /* * Report results to the cumulative stats system, too. * @@ -588,7 +736,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, rel->rd_rel->relisshared, Max(vacrel->new_live_tuples, 0), vacrel->recently_dead_tuples + - vacrel->missed_dead_tuples); + vacrel->missed_dead_tuples, + &extVacReport); pgstat_progress_end_command(); if (instrument) @@ -1370,6 +1519,8 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno, vmbuffer, InvalidTransactionId, VISIBILITYMAP_ALL_VISIBLE | VISIBILITYMAP_ALL_FROZEN); END_CRIT_SECTION(); + vacrel->set_all_visible_pages++; + vacrel->set_frozen_pages++; } freespace = PageGetHeapFreeSpace(page); @@ -2262,11 +2413,13 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer, &all_frozen)) { uint8 flags = VISIBILITYMAP_ALL_VISIBLE; + vacrel->set_all_visible_pages++; if (all_frozen) { Assert(!TransactionIdIsValid(visibility_cutoff_xid)); flags |= VISIBILITYMAP_ALL_FROZEN; + vacrel->set_frozen_pages++; } PageSetAllVisible(page); @@ -3101,6 +3254,8 @@ vacuum_error_callback(void *arg) switch (errinfo->phase) { case VACUUM_ERRCB_PHASE_SCAN_HEAP: + if(geterrelevel() >= ERROR) + pgstat_report_vacuum_error(errinfo->reloid); if (BlockNumberIsValid(errinfo->blkno)) { if (OffsetNumberIsValid(errinfo->offnum)) @@ -3116,6 +3271,8 @@ vacuum_error_callback(void *arg) break; case VACUUM_ERRCB_PHASE_VACUUM_HEAP: + if(geterrelevel() >= ERROR) + pgstat_report_vacuum_error(errinfo->reloid); if (BlockNumberIsValid(errinfo->blkno)) { if (OffsetNumberIsValid(errinfo->offnum)) diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 8b24e7bc33..2588bb84c8 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -96,6 +96,7 @@ #include "storage/smgr.h" #include "utils/inval.h" #include "utils/rel.h" +#include "pgstat.h" /*#define TRACE_VISIBILITYMAP */ @@ -160,6 +161,18 @@ visibilitymap_clear(Relation rel, BlockNumber heapBlk, Buffer vmbuf, uint8 flags if (map[mapByte] & mask) { + /* + * Initially, it didn't matter what type of flags (all-visible or frozen) we received, + * we just performed a reverse concatenation operation. But this information is very important + * for vacuum statistics. We need to find out this usingthe bit concatenation operation + * with the VISIBILITYMAP_ALL_VISIBLE and VISIBILITYMAP_ALL_FROZEN masks, + * and where the desired one matches, we increment the value there. + */ + if (map[mapByte] >> mapOffset & flags & VISIBILITYMAP_ALL_VISIBLE) + pgstat_count_vm_rev_all_visible(rel); + if (map[mapByte] >> mapOffset & flags & VISIBILITYMAP_ALL_FROZEN) + pgstat_count_vm_rev_all_frozen(rel); + map[mapByte] &= ~mask; MarkBufferDirty(vmbuf); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 53047cab5f..5deed61054 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1374,3 +1374,57 @@ CREATE VIEW pg_stat_subscription_stats AS CREATE VIEW pg_wait_events AS SELECT * FROM pg_get_wait_events(); +-- +-- Show extended cumulative statistics on a vacuum operation over all tables and +-- databases of the instance. +-- Use Invalid Oid "0" as an input relation id to get stat on each table in a +-- database. +-- + +CREATE VIEW pg_stats_vacuum_tables AS +SELECT + rel.oid as relid, + ns.nspname AS "schema", + rel.relname AS relname, + + stats.total_blks_read, + stats.total_blks_hit, + stats.total_blks_dirtied, + stats.total_blks_written, + + stats.rel_blks_read, + stats.rel_blks_hit, + + stats.pages_scanned, + stats.pages_removed, + stats.pages_frozen, + stats.pages_all_visible, + stats.tuples_deleted, + stats.tuples_frozen, + stats.dead_tuples, + + stats.index_vacuum_count, + stats.rev_all_frozen_pages, + stats.rev_all_visible_pages, + + stats.wal_records, + stats.wal_fpi, + stats.wal_bytes, + + stats.blk_read_time, + stats.blk_write_time, + + stats.delay_time, + stats.system_time, + stats.user_time, + stats.total_time, + stats.interrupts +FROM + pg_database db, + pg_class rel, + pg_namespace ns, + pg_stats_vacuum_tables(db.oid, rel.oid) stats +WHERE + db.datname = current_database() AND + rel.oid = stats.relid AND + ns.oid = rel.relnamespace; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 48f8eab202..ca1f4f8018 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -103,6 +103,9 @@ pg_atomic_uint32 *VacuumSharedCostBalance = NULL; pg_atomic_uint32 *VacuumActiveNWorkers = NULL; int VacuumCostBalanceLocal = 0; +/* Cumulative storage to report total vacuum delay time. */ +double VacuumDelayTime = 0; /* msec. */ + /* non-export function prototypes */ static List *expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, int options); @@ -2397,6 +2400,7 @@ vacuum_delay_point(void) exit(1); VacuumCostBalance = 0; + VacuumDelayTime += msec; /* * Balance and update limit values for autovacuum workers. We must do diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index f26070bff2..f421489241 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -1046,6 +1046,7 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) VacuumPageHit = 0; VacuumPageMiss = 0; VacuumPageDirty = 0; + VacuumDelayTime = 0; VacuumCostBalanceLocal = 0; VacuumSharedCostBalance = &(shared->cost_balance); VacuumActiveNWorkers = &(shared->active_nworkers); diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index dcc2ad8d95..56f4daafbe 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -127,36 +127,6 @@ #define PGSTAT_SNAPSHOT_HASH_SIZE 512 - -/* hash table for statistics snapshots entry */ -typedef struct PgStat_SnapshotEntry -{ - PgStat_HashKey key; - char status; /* for simplehash use */ - void *data; /* the stats data itself */ -} PgStat_SnapshotEntry; - - -/* ---------- - * Backend-local Hash Table Definitions - * ---------- - */ - -/* for stats snapshot entries */ -#define SH_PREFIX pgstat_snapshot -#define SH_ELEMENT_TYPE PgStat_SnapshotEntry -#define SH_KEY_TYPE PgStat_HashKey -#define SH_KEY key -#define SH_HASH_KEY(tb, key) \ - pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL) -#define SH_EQUAL(tb, a, b) \ - pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0 -#define SH_SCOPE static inline -#define SH_DEFINE -#define SH_DECLARE -#include "lib/simplehash.h" - - /* ---------- * Local function forward declarations * ---------- @@ -170,7 +140,7 @@ static void pgstat_reset_after_failure(void); static bool pgstat_flush_pending_entries(bool nowait); static void pgstat_prep_snapshot(void); -static void pgstat_build_snapshot(void); +static void pgstat_build_snapshot(PgStat_Kind statKind); static void pgstat_build_snapshot_fixed(PgStat_Kind kind); static inline bool pgstat_is_kind_valid(int ikind); @@ -764,6 +734,40 @@ pgstat_reset_of_kind(PgStat_Kind kind) pgstat_reset_entries_of_kind(kind, ts); } +void +pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src, + bool accumulate_reltype_specific_info) +{ + dst->total_blks_read += src->total_blks_read; + dst->total_blks_hit += src->total_blks_hit; + dst->total_blks_dirtied += src->total_blks_dirtied; + dst->total_blks_written += src->total_blks_written; + dst->wal_bytes += src->wal_bytes; + dst->wal_fpi += src->wal_fpi; + dst->wal_records += src->wal_records; + dst->blk_read_time += src->blk_read_time; + dst->blk_write_time += src->blk_write_time; + dst->delay_time += src->delay_time; + dst->system_time += src->system_time; + dst->user_time += src->user_time; + dst->total_time += src->total_time; + dst->interrupts += src->interrupts; + + if (!accumulate_reltype_specific_info) + return; + + dst->blks_fetched += src->blks_fetched; + dst->blks_hit += src->blks_hit; + + dst->pages_scanned += src->pages_scanned; + dst->pages_removed += src->pages_removed; + dst->pages_frozen += src->pages_frozen; + dst->pages_all_visible += src->pages_all_visible; + dst->tuples_deleted += src->tuples_deleted; + dst->tuples_frozen += src->tuples_frozen; + dst->dead_tuples += src->dead_tuples; + dst->index_vacuum_count += src->index_vacuum_count; +} /* ------------------------------------------------------------ * Fetching of stats @@ -828,7 +832,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid) /* if we need to build a full snapshot, do so */ if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) - pgstat_build_snapshot(); + pgstat_build_snapshot(PGSTAT_KIND_INVALID); /* if caching is desired, look up in cache */ if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE) @@ -944,7 +948,7 @@ pgstat_snapshot_fixed(PgStat_Kind kind) pgstat_clear_snapshot(); if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) - pgstat_build_snapshot(); + pgstat_build_snapshot(PGSTAT_KIND_INVALID); else pgstat_build_snapshot_fixed(kind); @@ -972,8 +976,30 @@ pgstat_prep_snapshot(void) NULL); } + +/* + * Trivial external interface to build a snapshot for table statistics only. + */ +void +pgstat_update_snapshot(PgStat_Kind kind) +{ + int save_consistency_guc = pgstat_fetch_consistency; + pgstat_clear_snapshot(); + + PG_TRY(); + { + pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT; + pgstat_build_snapshot(PGSTAT_KIND_RELATION); + } + PG_FINALLY(); + { + pgstat_fetch_consistency = save_consistency_guc; + } + PG_END_TRY(); +} + static void -pgstat_build_snapshot(void) +pgstat_build_snapshot(PgStat_Kind statKind) { dshash_seq_status hstat; PgStatShared_HashEntry *p; diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index 8a3f7d434c..d40d43cdb4 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -204,12 +204,40 @@ pgstat_drop_relation(Relation rel) } } +/* --------- + * pgstat_report_vacuum_error() - + * + * Tell the collector about an (auto)vacuum interruption. + * --------- + */ +void +pgstat_report_vacuum_error(Oid tableoid) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + Oid dboid = MyDatabaseId; + + if (!pgstat_track_counts) + return; + + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + dboid, tableoid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + tabentry->vacuum_ext.interrupts++; + pgstat_unlock_entry(entry_ref); +} + /* * Report that the table was just vacuumed and flush IO statistics. */ void pgstat_report_vacuum(Oid tableoid, bool shared, - PgStat_Counter livetuples, PgStat_Counter deadtuples) + PgStat_Counter livetuples, PgStat_Counter deadtuples, + ExtVacReport *params) { PgStat_EntryRef *entry_ref; PgStatShared_Relation *shtabentry; @@ -233,6 +261,8 @@ pgstat_report_vacuum(Oid tableoid, bool shared, tabentry->live_tuples = livetuples; tabentry->dead_tuples = deadtuples; + pgstat_accumulate_extvac_stats(&tabentry->vacuum_ext, params, true); + /* * It is quite possible that a non-aggressive VACUUM ended up skipping * various pages, however, we'll zero the insert counter here regardless. @@ -861,6 +891,9 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) tabentry->blocks_fetched += lstats->counts.blocks_fetched; tabentry->blocks_hit += lstats->counts.blocks_hit; + tabentry->rev_all_frozen_pages += lstats->counts.rev_all_frozen_pages; + tabentry->rev_all_visible_pages += lstats->counts.rev_all_visible_pages; + /* Clamp live_tuples in case of negative delta_live_tuples */ tabentry->live_tuples = Max(tabentry->live_tuples, 0); /* Likewise for dead_tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 3876339ee1..3c9f0037d4 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -31,6 +31,7 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/timestamp.h" +#include "utils/pgstat_internal.h" #define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var)))) @@ -2032,3 +2033,188 @@ pg_stat_have_stats(PG_FUNCTION_ARGS) PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid)); } + +#define EXTVACHEAPSTAT_COLUMNS 27 + +static Oid CurrentDatabaseId = InvalidOid; + + +/* + * Fetch stat collector data for specific database and table, which loading from disc. + * It is maybe expensive, but i guess we won't use that machinery often. + * The kind of bufferization is based on CurrentDatabaseId value. + */ +static PgStat_StatTabEntry * +fetch_dbstat_tabentry(Oid dbid, Oid relid) +{ + Oid storedMyDatabaseId = MyDatabaseId; + PgStat_StatTabEntry *tabentry = NULL; + + if (OidIsValid(CurrentDatabaseId) && CurrentDatabaseId == dbid) + /* Quick path when we read data from the same database */ + return pgstat_fetch_stat_tabentry(relid); + + pgstat_clear_snapshot(); + + /* Tricky turn here: enforce pgstat to think that our database has dbid */ + + MyDatabaseId = dbid; + + PG_TRY(); + { + tabentry = pgstat_fetch_stat_tabentry(relid); + MyDatabaseId = storedMyDatabaseId; + } + PG_CATCH(); + { + MyDatabaseId = storedMyDatabaseId; + } + PG_END_TRY(); + + return tabentry; +} + +static void +tuplestore_put_for_relation(Oid relid, Tuplestorestate *tupstore, + TupleDesc tupdesc, PgStat_StatTabEntry *tabentry, int ncolumns) +{ + Datum values[EXTVACHEAPSTAT_COLUMNS]; + bool nulls[EXTVACHEAPSTAT_COLUMNS]; + char buf[256]; + int i = 0; + + memset(nulls, 0, EXTVACHEAPSTAT_COLUMNS * sizeof(bool)); + + values[i++] = ObjectIdGetDatum(relid); + + values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_read); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_hit); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_dirtied); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.total_blks_written); + + values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_fetched - + tabentry->vacuum_ext.blks_hit); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.blks_hit); + + values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_scanned); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_removed); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_frozen); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.pages_all_visible); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.tuples_deleted); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.tuples_frozen); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.dead_tuples); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.index_vacuum_count); + values[i++] = Int64GetDatum(tabentry->rev_all_frozen_pages); + values[i++] = Int64GetDatum(tabentry->rev_all_visible_pages); + + values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_records); + values[i++] = Int64GetDatum(tabentry->vacuum_ext.wal_fpi); + + /* Convert to numeric, like pg_stat_statements */ + snprintf(buf, sizeof buf, UINT64_FORMAT, tabentry->vacuum_ext.wal_bytes); + values[i++] = DirectFunctionCall3(numeric_in, + CStringGetDatum(buf), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + + values[i++] = Float8GetDatum(tabentry->vacuum_ext.blk_read_time); + values[i++] = Float8GetDatum(tabentry->vacuum_ext.blk_write_time); + values[i++] = Float8GetDatum(tabentry->vacuum_ext.delay_time); + values[i++] = Float8GetDatum(tabentry->vacuum_ext.system_time); + values[i++] = Float8GetDatum(tabentry->vacuum_ext.user_time); + values[i++] = Float8GetDatum(tabentry->vacuum_ext.total_time); + values[i++] = Int32GetDatum(tabentry->vacuum_ext.interrupts); + + Assert(i == ncolumns); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); +} + +/* + * Get the vacuum statistics for the heap tables or indexes. + */ +static Datum +pg_stats_vacuum(FunctionCallInfo fcinfo, int ncolumns) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + Tuplestorestate *tupstore; + TupleDesc tupdesc; + Oid dbid = PG_GETARG_OID(0); + Oid relid = PG_GETARG_OID(1); + PgStat_StatTabEntry *tabentry; + + InitMaterializedSRF(fcinfo, 0); + + /* Check if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + /* Switch to long-lived context to create the returned data structures */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + Assert(tupdesc->natts == ncolumns); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + Assert (tupstore != NULL); + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + /* Load table statistics for specified database. */ + if (OidIsValid(relid)) + { + tabentry = fetch_dbstat_tabentry(dbid, relid); + if (tabentry == NULL) + /* Table don't exists or isn't an heap relation. */ + PG_RETURN_NULL(); + + tuplestore_put_for_relation(relid, tupstore, tupdesc, tabentry, ncolumns); + } + else + { + SnapshotIterator hashiter; + PgStat_SnapshotEntry *entry; + Oid storedMyDatabaseId = MyDatabaseId; + + pgstat_update_snapshot(PGSTAT_KIND_RELATION); + MyDatabaseId = storedMyDatabaseId; + + + /* Iterate the snapshot */ + InitSnapshotIterator(pgStatLocal.snapshot.stats, &hashiter); + + while ((entry = ScanStatSnapshot(pgStatLocal.snapshot.stats, &hashiter)) != NULL) + { + Oid reloid; + + CHECK_FOR_INTERRUPTS(); + + tabentry = (PgStat_StatTabEntry *) entry->data; + reloid = entry->key.objoid; + + if (tabentry != NULL) + tuplestore_put_for_relation(reloid, tupstore, tupdesc, tabentry, ncolumns); + } + } + PG_RETURN_NULL(); +} + +/* + * Get the vacuum statistics for the heap tables. + */ +Datum +pg_stats_vacuum_tables(PG_FUNCTION_ARGS) +{ + return pg_stats_vacuum(fcinfo, EXTVACHEAPSTAT_COLUMNS); + + PG_RETURN_NULL(); +} diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index d91a85cb2d..3cee3436d9 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -1602,6 +1602,19 @@ getinternalerrposition(void) return edata->internalpos; } +/* + * Return elevel of errors + */ +int +geterrelevel(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + return edata->elevel; +} /* * Functions to allow construction of error message strings separately from diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6a5476d3c4..6ccd350213 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12184,5 +12184,13 @@ proallargtypes => '{int8,pg_lsn,pg_lsn,int4}', proargmodes => '{o,o,o,o}', proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}', prosrc => 'pg_get_wal_summarizer_state' }, - +{ oid => '4701', + descr => 'pg_stats_vacuum_tables return stats values', + proname => 'pg_stats_vacuum_tables', provolatile => 's', prorettype => 'record',proisstrict => 'f', + proretset => 't', + proargtypes => 'oid oid', + proallargtypes => '{oid,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,numeric,float8,float8,float8,float8,float8,float8,int4}', + proargmodes => '{i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{dboid,reloid,relid,total_blks_read,total_blks_hit,total_blks_dirtied,total_blks_written,rel_blks_read,rel_blks_hit,pages_scanned,pages_removed,pages_frozen,pages_all_visible,tuples_deleted,tuples_frozen,dead_tuples,index_vacuum_count,rev_all_frozen_pages,rev_all_visible_pages,wal_records,wal_fpi,wal_bytes,blk_read_time,blk_write_time,delay_time,system_time,user_time,total_time,interrupts}', + prosrc => 'pg_stats_vacuum_tables' }, ] diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 759f9a87d3..07b28b15d9 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -308,6 +308,7 @@ extern PGDLLIMPORT int vacuum_multixact_failsafe_age; extern PGDLLIMPORT pg_atomic_uint32 *VacuumSharedCostBalance; extern PGDLLIMPORT pg_atomic_uint32 *VacuumActiveNWorkers; extern PGDLLIMPORT int VacuumCostBalanceLocal; +extern PGDLLIMPORT double VacuumDelayTime; extern PGDLLIMPORT bool VacuumFailsafeActive; extern PGDLLIMPORT double vacuum_cost_delay; diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 2136239710..65b030b336 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -137,6 +137,52 @@ typedef struct PgStat_BackendSubEntry PgStat_Counter sync_error_count; } PgStat_BackendSubEntry; +/* ---------- + * + * ExtVacReport + * + * Additional statistics of vacuum processing over a heap relation. + * pages_removed is the amount by which the physically shrank, + * if any (ie the change in its total size on disk) + * pages_deleted refer to free space within the index file + * ---------- + */ +typedef struct ExtVacReport +{ + int64 total_blks_read; /* number of pages that were missed in shared buffers during a vacuum of specific relation */ + int64 total_blks_hit; /* number of pages that were found in shared buffers during a vacuum of specific relation */ + int64 total_blks_dirtied; /* number of pages marked as 'Dirty' during a vacuum of specific relation. */ + int64 total_blks_written; /* number of pages written during a vacuum of specific relation. */ + + int64 blks_fetched; /* number of a relation blocks, fetched during the vacuum. */ + int64 blks_hit; /* number of a relation blocks, found in shared buffers during the vacuum. */ + + /* Vacuum WAL usage stats */ + int64 wal_records; /* wal usage: number of WAL records */ + int64 wal_fpi; /* wal usage: number of WAL full page images produced */ + uint64 wal_bytes; /* wal usage: size of WAL records produced */ + + /* Time stats. */ + double blk_read_time; /* time spent reading pages, in msec */ + double blk_write_time; /* time spent writing pages, in msec */ + double delay_time; /* how long vacuum slept in vacuum delay point, in msec */ + double system_time; /* amount of time the CPU was busy executing vacuum code in kernel space, in msec */ + double user_time; /* amount of time the CPU was busy executing vacuum code in user space, in msec */ + double total_time; /* total time of a vacuum operation, in msec */ + + /* Interruptions on any errors. */ + int32 interrupts; + + int64 pages_scanned; /* number of pages we examined */ + int64 pages_removed; /* number of pages removed by vacuum */ + int64 pages_frozen; /* number of pages marked in VM as frozen */ + int64 pages_all_visible; /* number of pages marked in VM as all-visible */ + int64 tuples_deleted; /* tuples deleted by vacuum */ + int64 tuples_frozen; /* tuples frozen up by vacuum */ + int64 dead_tuples; /* number of deleted tuples which vacuum cannot clean up by vacuum operation */ + int64 index_vacuum_count; /* number of index vacuumings */ +} ExtVacReport; + /* ---------- * PgStat_TableCounts The actual per-table counts kept by a backend * @@ -177,6 +223,16 @@ typedef struct PgStat_TableCounts PgStat_Counter blocks_fetched; PgStat_Counter blocks_hit; + + PgStat_Counter rev_all_visible_pages; + PgStat_Counter rev_all_frozen_pages; + + /* + * Additional cumulative stat on vacuum operations. + * Use an expensive structure as an abstraction for different types of + * relations. + */ + ExtVacReport vacuum_ext; } PgStat_TableCounts; /* ---------- @@ -235,7 +291,7 @@ typedef struct PgStat_TableXactStatus * ------------------------------------------------------------ */ -#define PGSTAT_FILE_FORMAT_ID 0x01A5BCAC +#define PGSTAT_FILE_FORMAT_ID 0x01A5BCAD typedef struct PgStat_ArchiverStats { @@ -354,6 +410,8 @@ typedef struct PgStat_StatDBEntry PgStat_Counter sessions_killed; TimestampTz stat_reset_timestamp; + + ExtVacReport vacuum_ext; /* extended vacuum statistics */ } PgStat_StatDBEntry; typedef struct PgStat_StatFuncEntry @@ -426,6 +484,11 @@ typedef struct PgStat_StatTabEntry PgStat_Counter analyze_count; TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + + PgStat_Counter rev_all_visible_pages; + PgStat_Counter rev_all_frozen_pages; + + ExtVacReport vacuum_ext; } PgStat_StatTabEntry; typedef struct PgStat_WalStats @@ -591,10 +654,12 @@ extern void pgstat_assoc_relation(Relation rel); extern void pgstat_unlink_relation(Relation rel); extern void pgstat_report_vacuum(Oid tableoid, bool shared, - PgStat_Counter livetuples, PgStat_Counter deadtuples); + PgStat_Counter livetuples, PgStat_Counter deadtuples, + ExtVacReport *params); extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter); +extern void pgstat_report_vacuum_error(Oid tableoid); /* * If stats are enabled, but pending data hasn't been prepared yet, call @@ -642,6 +707,17 @@ extern void pgstat_report_analyze(Relation rel, if (pgstat_should_count_relation(rel)) \ (rel)->pgstat_info->counts.blocks_hit++; \ } while (0) +/* accumulate unfrozen all-visible and all-frozen pages */ +#define pgstat_count_vm_rev_all_visible(rel) \ + do { \ + if (pgstat_should_count_relation(rel)) \ + (rel)->pgstat_info->counts.rev_all_visible_pages++; \ + } while (0) +#define pgstat_count_vm_rev_all_frozen(rel) \ + do { \ + if (pgstat_should_count_relation(rel)) \ + (rel)->pgstat_info->counts.rev_all_frozen_pages++; \ + } while (0) extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n); extern void pgstat_count_heap_update(Relation rel, bool hot, bool newpage); @@ -658,7 +734,9 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid); extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid); extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id); - +extern void +pgstat_accumulate_extvac_stats(ExtVacReport *dst, ExtVacReport *src, + bool accumulate_reltype_specific_info); /* * Functions in pgstat_replslot.c diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 054dd2bf62..c6225b9cdd 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -228,7 +228,7 @@ extern int err_generic_string(int field, const char *str); extern int geterrcode(void); extern int geterrposition(void); extern int getinternalerrposition(void); - +extern int geterrelevel(void); /*---------- * Old-style error reporting API: to be used in this way: diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index dbbca31602..e6079c6742 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -516,7 +516,7 @@ extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid); extern void pgstat_snapshot_fixed(PgStat_Kind kind); - +extern void pgstat_update_snapshot(PgStat_Kind kind); /* * Functions in pgstat_archiver.c @@ -805,4 +805,38 @@ pgstat_get_entry_data(PgStat_Kind kind, PgStatShared_Common *entry) return ((char *) (entry)) + off; } +/* hash table for statistics snapshots entry */ +typedef struct PgStat_SnapshotEntry +{ + PgStat_HashKey key; + char status; /* for simplehash use */ + void *data; /* the stats data itself */ +} PgStat_SnapshotEntry; + +/* ---------- + * Backend-local Hash Table Definitions + * ---------- + */ + +/* for stats snapshot entries */ +#define SH_PREFIX pgstat_snapshot +#define SH_ELEMENT_TYPE PgStat_SnapshotEntry +#define SH_KEY_TYPE PgStat_HashKey +#define SH_KEY key +#define SH_HASH_KEY(tb, key) \ + pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL) +#define SH_EQUAL(tb, a, b) \ + pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0 +#define SH_SCOPE static inline +#define SH_DEFINE +#define SH_DECLARE +#include "lib/simplehash.h" + +typedef pgstat_snapshot_iterator SnapshotIterator; + +#define InitSnapshotIterator(htable, iter) \ + pgstat_snapshot_start_iterate(htable, iter); +#define ScanStatSnapshot(htable, iter) \ + pgstat_snapshot_iterate(htable, iter) + #endif /* PGSTAT_INTERNAL_H */ diff --git a/src/test/isolation/expected/vacuum-extending-in-repetable-read.out b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out new file mode 100644 index 0000000000..eafe1a1461 --- /dev/null +++ b/src/test/isolation/expected/vacuum-extending-in-repetable-read.out @@ -0,0 +1,53 @@ +unused step name: s2_delete +Parsed test spec with 2 sessions + +starting permutation: s2_insert s2_print_vacuum_stats_table s1_begin_repeatable_read s2_update s2_insert_interrupt s2_vacuum s2_print_vacuum_stats_table s1_commit s2_checkpoint s2_vacuum s2_print_vacuum_stats_table +step s2_insert: INSERT INTO test_vacuum_stat_isolation(id, ival) SELECT ival, ival%10 FROM generate_series(1,1000) As ival; +step s2_print_vacuum_stats_table: + SELECT + vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen + FROM pg_stats_vacuum_tables vt, pg_class c + WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid; + +relname |tuples_deleted|dead_tuples|tuples_frozen +--------------------------+--------------+-----------+------------- +test_vacuum_stat_isolation| 0| 0| 0 +(1 row) + +step s1_begin_repeatable_read: + BEGIN transaction ISOLATION LEVEL REPEATABLE READ; + select count(ival) from test_vacuum_stat_isolation where id>900; + +count +----- + 100 +(1 row) + +step s2_update: UPDATE test_vacuum_stat_isolation SET ival = ival + 2 where id > 900; +step s2_insert_interrupt: INSERT INTO test_vacuum_stat_isolation values (1,1); +step s2_vacuum: VACUUM test_vacuum_stat_isolation; +step s2_print_vacuum_stats_table: + SELECT + vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen + FROM pg_stats_vacuum_tables vt, pg_class c + WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid; + +relname |tuples_deleted|dead_tuples|tuples_frozen +--------------------------+--------------+-----------+------------- +test_vacuum_stat_isolation| 0| 100| 0 +(1 row) + +step s1_commit: COMMIT; +step s2_checkpoint: CHECKPOINT; +step s2_vacuum: VACUUM test_vacuum_stat_isolation; +step s2_print_vacuum_stats_table: + SELECT + vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen + FROM pg_stats_vacuum_tables vt, pg_class c + WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid; + +relname |tuples_deleted|dead_tuples|tuples_frozen +--------------------------+--------------+-----------+------------- +test_vacuum_stat_isolation| 100| 100| 101 +(1 row) + diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 0342eb39e4..5bac4ddbe1 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -92,6 +92,7 @@ test: timeouts test: vacuum-concurrent-drop test: vacuum-conflict test: vacuum-skip-locked +test: vacuum-extending-in-repetable-read test: stats test: horizons test: predicate-hash diff --git a/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec new file mode 100644 index 0000000000..655051b512 --- /dev/null +++ b/src/test/isolation/specs/vacuum-extending-in-repetable-read.spec @@ -0,0 +1,51 @@ +# Test for checking dead_tuples, tuples_deleted and frozen tuples in pg_stats_vacuum_tables. +# Dead_tuples values are counted when vacuum cannot clean up unused tuples while lock is using another transaction. +# Dead_tuples aren't increased after releasing lock compared with tuples_deleted, which increased +# by the value of the cleared tuples that the vacuum managed to clear. + +setup +{ + CREATE TABLE test_vacuum_stat_isolation(id int, ival int) WITH (autovacuum_enabled = off); + SET track_io_timing = on; +} + +teardown +{ + DROP TABLE test_vacuum_stat_isolation CASCADE; + RESET track_io_timing; +} + +session s1 +step s1_begin_repeatable_read { + BEGIN transaction ISOLATION LEVEL REPEATABLE READ; + select count(ival) from test_vacuum_stat_isolation where id>900; + } +step s1_commit { COMMIT; } + +session s2 +step s2_insert { INSERT INTO test_vacuum_stat_isolation(id, ival) SELECT ival, ival%10 FROM generate_series(1,1000) As ival; } +step s2_update { UPDATE test_vacuum_stat_isolation SET ival = ival + 2 where id > 900; } +step s2_delete { DELETE FROM test_vacuum_stat_isolation where id > 900; } +step s2_insert_interrupt { INSERT INTO test_vacuum_stat_isolation values (1,1); } +step s2_vacuum { VACUUM test_vacuum_stat_isolation; } +step s2_checkpoint { CHECKPOINT; } +step s2_print_vacuum_stats_table +{ + SELECT + vt.relname, vt.tuples_deleted, vt.dead_tuples, vt.tuples_frozen + FROM pg_stats_vacuum_tables vt, pg_class c + WHERE vt.relname = 'test_vacuum_stat_isolation' AND vt.relid = c.oid; +} + +permutation + s2_insert + s2_print_vacuum_stats_table + s1_begin_repeatable_read + s2_update + s2_insert_interrupt + s2_vacuum + s2_print_vacuum_stats_table + s1_commit + s2_checkpoint + s2_vacuum + s2_print_vacuum_stats_table diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 9d047b21b8..72f677ab40 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -32,9 +32,10 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR prokind NOT IN ('f', 'a', 'w', 'p') OR provolatile NOT IN ('i', 's', 'v') OR proparallel NOT IN ('s', 'r', 'u'); - oid | proname ------+--------- -(0 rows) + oid | proname +------+------------------------ + 4701 | pg_stats_vacuum_tables +(1 row) -- prosrc should never be null; it can be empty only if prosqlbody isn't null SELECT p1.oid, p1.proname diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index ef658ad740..201b2bc1b7 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2606,6 +2606,40 @@ pg_stats_ext_exprs| SELECT cn.nspname AS schemaname, JOIN LATERAL ( SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr, unnest(sd.stxdexpr) AS a) stat ON ((stat.expr IS NOT NULL))) WHERE (pg_has_role(c.relowner, 'USAGE'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_stats_vacuum_tables| SELECT rel.oid AS relid, + ns.nspname AS schema, + rel.relname, + stats.total_blks_read, + stats.total_blks_hit, + stats.total_blks_dirtied, + stats.total_blks_written, + stats.rel_blks_read, + stats.rel_blks_hit, + stats.pages_scanned, + stats.pages_removed, + stats.pages_frozen, + stats.pages_all_visible, + stats.tuples_deleted, + stats.tuples_frozen, + stats.dead_tuples, + stats.index_vacuum_count, + stats.rev_all_frozen_pages, + stats.rev_all_visible_pages, + stats.wal_records, + stats.wal_fpi, + stats.wal_bytes, + stats.blk_read_time, + stats.blk_write_time, + stats.delay_time, + stats.system_time, + stats.user_time, + stats.total_time, + stats.interrupts + FROM pg_database db, + pg_class rel, + pg_namespace ns, + LATERAL pg_stats_vacuum_tables(db.oid, rel.oid) stats(relid, total_blks_read, total_blks_hit, total_blks_dirtied, total_blks_written, rel_blks_read, rel_blks_hit, pages_scanned, pages_removed, pages_frozen, pages_all_visible, tuples_deleted, tuples_frozen, dead_tuples, index_vacuum_count, rev_all_frozen_pages, rev_all_visible_pages, wal_records, wal_fpi, wal_bytes, blk_read_time, blk_write_time, delay_time, system_time, user_time, total_time, interrupts) + WHERE ((db.datname = current_database()) AND (rel.oid = stats.relid) AND (ns.oid = rel.relnamespace)); pg_tables| SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, diff --git a/src/test/regress/expected/vacuum_tables_statistics.out b/src/test/regress/expected/vacuum_tables_statistics.out new file mode 100644 index 0000000000..536441b36e --- /dev/null +++ b/src/test/regress/expected/vacuum_tables_statistics.out @@ -0,0 +1,164 @@ +-- +-- 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. +-- +CREATE DATABASE statistic_vacuum_database; +CREATE DATABASE statistic_vacuum_database1; +\c statistic_vacuum_database; +-- conditio sine qua non +SHOW track_counts; -- must be on + track_counts +-------------- + on +(1 row) + +-- not enabled by default, but we want to test it... +SET track_functions TO 'all'; +-- ensure pending stats are flushed +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +\set sample_size 10000 +SET vacuum_freeze_min_age = 0; +SET vacuum_freeze_table_age = 0; +--SET stats_fetch_consistency = snapshot; +CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +ANALYZE vestat; +SELECT oid AS roid from pg_class where relname = 'vestat' \gset +DELETE FROM vestat WHERE x % 2 = 0; +-- Before the first vacuum execution extended stats view is empty. +SELECT vt.relname,pages_frozen,tuples_deleted,relpages,pages_scanned,pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; + relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed +---------+--------------+----------------+----------+---------------+--------------- + vestat | 0 | 0 | 455 | 0 | 0 +(1 row) + +SELECT relpages AS rp +FROM pg_class c +WHERE relname = 'vestat' \gset +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; +-- The table and index extended vacuum statistics should show us that +-- vacuum frozed pages and clean up pages, but pages_removed stayed the same +-- because of not full table have cleaned up +SELECT vt.relname,pages_frozen > 0 AS pages_frozen,tuples_deleted > 0 AS tuples_deleted,relpages-:rp = 0 AS relpages,pages_scanned > 0 AS pages_scanned,pages_removed = 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; + relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed +---------+--------------+----------------+----------+---------------+--------------- + vestat | f | t | t | t | t +(1 row) + +SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset +-- Store WAL advances into variables +SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset +-- Look into WAL records deltas. +SELECT wal_records > 0 AS dWR, wal_bytes > 0 AS dWB +FROM pg_stats_vacuum_tables WHERE relname = 'vestat'; + dwr | dwb +-----+----- + t | t +(1 row) + +DELETE FROM vestat;; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; +-- pages_removed must be increased +SELECT vt.relname,pages_frozen-:fp > 0 AS pages_frozen,tuples_deleted-:td > 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps > 0 AS pages_scanned,pages_removed-:pr > 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; + relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed +---------+--------------+----------------+----------+---------------+--------------- + vestat | f | t | f | t | t +(1 row) + +SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset +-- Store WAL advances into variables +SELECT wal_records-:hwr AS dwr, wal_bytes-:hwb AS dwb, wal_fpi-:hfpi AS dfpi +FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset +-- WAL advance should be detected. +SELECT :dwr > 0 AS dWR, :dwb > 0 AS dWB; + dwr | dwb +-----+----- + t | t +(1 row) + +-- Store WAL advances into variables +SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +DELETE FROM vestat WHERE x % 2 = 0; +-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics +-- are detected here. +VACUUM FULL vestat; +-- It is necessary to check the wal statistics +CHECKPOINT; +-- Store WAL advances into variables +SELECT wal_records-:hwr AS dwr2, wal_bytes-:hwb AS dwb2, wal_fpi-:hfpi AS dfpi2 +FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset +-- WAL and other statistics advance should not be detected. +SELECT :dwr2=0 AS dWR, :dfpi2=0 AS dFPI, :dwb2=0 AS dWB; + dwr | dfpi | dwb +-----+------+----- + t | t | t +(1 row) + +SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp < 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; + relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed +---------+--------------+----------------+----------+---------------+--------------- + vestat | t | t | f | t | t +(1 row) + +SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps,pages_removed AS pr +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset +-- Store WAL advances into variables +SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset +DELETE FROM vestat; +TRUNCATE vestat; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; +-- Store WAL advances into variables after removing all tuples from the table +SELECT wal_records-:hwr AS dwr3, wal_bytes-:hwb AS dwb3, wal_fpi-:hfpi AS dfpi3 +FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset +--There are nothing changed +SELECT :dwr3>0 AS dWR, :dfpi3=0 AS dFPI, :dwb3>0 AS dWB; + dwr | dfpi | dwb +-----+------+----- + t | t | t +(1 row) + +-- +-- Now, the table and index is compressed into zero number of pages. Check it +-- in vacuum extended statistics. +-- The pages_frozen, pages_scanned values shouldn't be changed +-- +SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; + relname | pages_frozen | tuples_deleted | relpages | pages_scanned | pages_removed +---------+--------------+----------------+----------+---------------+--------------- + vestat | t | t | f | t | t +(1 row) + +DROP TABLE vestat CASCADE; +\c postgres +DROP DATABASE statistic_vacuum_database; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 969ced994f..45e20d30ab 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -136,3 +136,8 @@ test: fast_default # run tablespace test at the end because it drops the tablespace created during # setup that other tests may use. test: tablespace + +# ---------- +# Check vacuum statistics +# ---------- +test: vacuum_tables_statistics \ No newline at end of file diff --git a/src/test/regress/sql/vacuum_tables_statistics.sql b/src/test/regress/sql/vacuum_tables_statistics.sql new file mode 100644 index 0000000000..0ac4c7b2a2 --- /dev/null +++ b/src/test/regress/sql/vacuum_tables_statistics.sql @@ -0,0 +1,135 @@ +-- +-- 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. +-- + +CREATE DATABASE statistic_vacuum_database; +CREATE DATABASE statistic_vacuum_database1; +\c statistic_vacuum_database; +-- conditio sine qua non +SHOW track_counts; -- must be on +-- not enabled by default, but we want to test it... +SET track_functions TO 'all'; + + +-- ensure pending stats are flushed +SELECT pg_stat_force_next_flush(); + +\set sample_size 10000 +SET vacuum_freeze_min_age = 0; +SET vacuum_freeze_table_age = 0; +--SET stats_fetch_consistency = snapshot; +CREATE TABLE vestat (x int) WITH (autovacuum_enabled = off, fillfactor = 10); +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +ANALYZE vestat; + +SELECT oid AS roid from pg_class where relname = 'vestat' \gset + +DELETE FROM vestat WHERE x % 2 = 0; +-- Before the first vacuum execution extended stats view is empty. +SELECT vt.relname,pages_frozen,tuples_deleted,relpages,pages_scanned,pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; +SELECT relpages AS rp +FROM pg_class c +WHERE relname = 'vestat' \gset + +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; + +-- The table and index extended vacuum statistics should show us that +-- vacuum frozed pages and clean up pages, but pages_removed stayed the same +-- because of not full table have cleaned up +SELECT vt.relname,pages_frozen > 0 AS pages_frozen,tuples_deleted > 0 AS tuples_deleted,relpages-:rp = 0 AS relpages,pages_scanned > 0 AS pages_scanned,pages_removed = 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; +SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset + +-- Store WAL advances into variables +SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset + +-- Look into WAL records deltas. +SELECT wal_records > 0 AS dWR, wal_bytes > 0 AS dWB +FROM pg_stats_vacuum_tables WHERE relname = 'vestat'; + +DELETE FROM vestat;; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; + +-- pages_removed must be increased +SELECT vt.relname,pages_frozen-:fp > 0 AS pages_frozen,tuples_deleted-:td > 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps > 0 AS pages_scanned,pages_removed-:pr > 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; +SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps, pages_removed AS pr +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset + +-- Store WAL advances into variables +SELECT wal_records-:hwr AS dwr, wal_bytes-:hwb AS dwb, wal_fpi-:hfpi AS dfpi +FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset + +-- WAL advance should be detected. +SELECT :dwr > 0 AS dWR, :dwb > 0 AS dWB; + +-- Store WAL advances into variables +SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset + +INSERT INTO vestat SELECT x FROM generate_series(1,:sample_size) as x; +DELETE FROM vestat WHERE x % 2 = 0; +-- VACUUM FULL doesn't report to stat collector. So, no any advancements of statistics +-- are detected here. +VACUUM FULL vestat; +-- It is necessary to check the wal statistics +CHECKPOINT; + +-- Store WAL advances into variables +SELECT wal_records-:hwr AS dwr2, wal_bytes-:hwb AS dwb2, wal_fpi-:hfpi AS dfpi2 +FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset + +-- WAL and other statistics advance should not be detected. +SELECT :dwr2=0 AS dWR, :dfpi2=0 AS dFPI, :dwb2=0 AS dWB; + +SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp < 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; +SELECT pages_frozen AS fp,tuples_deleted AS td,relpages AS rp, pages_scanned AS ps,pages_removed AS pr +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid \gset + +-- Store WAL advances into variables +SELECT wal_records AS hwr,wal_bytes AS hwb,wal_fpi AS hfpi FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset + +DELETE FROM vestat; +TRUNCATE vestat; +VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128, INDEX_CLEANUP OFF) vestat; +-- it is necessary to check the wal statistics +CHECKPOINT; + +-- Store WAL advances into variables after removing all tuples from the table +SELECT wal_records-:hwr AS dwr3, wal_bytes-:hwb AS dwb3, wal_fpi-:hfpi AS dfpi3 +FROM pg_stats_vacuum_tables WHERE relname = 'vestat' \gset + +--There are nothing changed +SELECT :dwr3>0 AS dWR, :dfpi3=0 AS dFPI, :dwb3>0 AS dWB; + +-- +-- Now, the table and index is compressed into zero number of pages. Check it +-- in vacuum extended statistics. +-- The pages_frozen, pages_scanned values shouldn't be changed +-- +SELECT vt.relname,pages_frozen-:fp = 0 AS pages_frozen,tuples_deleted-:td = 0 AS tuples_deleted,relpages -:rp = 0 AS relpages,pages_scanned-:ps = 0 AS pages_scanned,pages_removed-:pr = 0 AS pages_removed +FROM pg_stats_vacuum_tables vt, pg_class c +WHERE vt.relname = 'vestat' AND vt.relid = c.oid; + +DROP TABLE vestat CASCADE; + +\c postgres +DROP DATABASE statistic_vacuum_database; \ No newline at end of file -- 2.34.1